-
Notifications
You must be signed in to change notification settings - Fork 0
✨feat : 홈 관련 기능 구현 #84
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
The head ref may contain hidden characters: "feat/\uD648-\uAD00\uB828-\uAE30\uB2A5-\uAD6C\uD604"
Conversation
개요새로운 홈 기능의 핵심 서비스 계층을 구현하고, 금액 단위를 원에서 만원으로 통일하며, 공고의 대상계층 정보를 응답 데이터에 추가하고, 교통 경로 표시를 개선합니다. 변경 사항
시퀀스 다이어그램sequenceDiagram
participant Client
participant HomeApi
participant HomeService
participant PinPointRepo as PinPoint<br/>Repository
participant NoticeRepo as NoticeDocument<br/>Repository
participant LikeQueryUseCase
Client->>HomeApi: GET /v1/home/notice<br/>(pinpointId, page, size)
HomeApi->>HomeService: getDeadlineApproachingNotices()
HomeService->>PinPointRepo: findById(pinpointId)
PinPointRepo-->>HomeService: PinPoint
HomeService->>HomeService: extractRegionFromAddress()
HomeService->>HomeService: extractCountyFromAddress()
HomeService->>NoticeRepo: findDeadlineApproachingNotices<br/>ByRegionAndCounty()
NoticeRepo-->>HomeService: Page<NoticeDocument>
HomeService->>LikeQueryUseCase: 좋아요 여부 조회
LikeQueryUseCase-->>HomeService: Set<String> (liked ids)
HomeService->>HomeService: 결과 매핑<br/>(isLiked 플래그 추가)
HomeService-->>HomeApi: SliceResponse<NoticeListResponse>
HomeApi-->>Client: ApiResponse
sequenceDiagram
participant Client
participant HomeApi
participant HomeService
participant NoticeSearchUseCase
Client->>HomeApi: GET /v1/home/search<br/>(keyword, page, sort, status)
HomeApi->>HomeService: searchNoticesIntegrated()
HomeService->>NoticeSearchUseCase: 키워드 기반 검색
NoticeSearchUseCase-->>HomeService: SliceResponse<result>
HomeService-->>HomeApi: 결과 반환
HomeApi-->>Client: ApiResponse
코드 리뷰 예상 소요 시간🎯 3 (보통) | ⏱️ ~20분 축시
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: 6
Fix all issues with AI Agents 🤖
In
@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java:
- Around line 70-71: The extracted county string from
extractCountyFromAddress(pinPoint.getAddress()) may contain regex metacharacters
and is later used in a Pattern in NoticeDocumentRepositoryImpl; escape it before
usage by quoting the value with Pattern.quote() (either wrap the county here in
HomeService before passing to the repository or update the repository method
that consumes the county to call Pattern.quote(county) when building the
regex/Pattern) so no unescaped user/address data is interpolated into regexes.
- Line 78: The code treats SliceRequest.page() as 1-based (HomeService uses
PageRequest.of(sliceRequest.page() - 1, ...)) while the Swagger schema says
pages are 0-based; fix by making the API contract 1-based and enforcing it:
update the SliceRequest Swagger/Schema description to say "페이지 번호 (1부터 시작)" and
add validation on the page field (e.g., @Min(1)) in the SliceRequest class so
clients cannot pass 0 or negative values that would cause PageRequest.of() to
receive a negative index; keep the existing subtraction in HomeService
(PageRequest.of(sliceRequest.page() - 1, ...)) so existing logic remains correct
once validation and docs are aligned.
In
@src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java:
- Around line 330-344: The departureLocation for the first WALK is being derived
from firstTransport/transportSteps which yields the wrong start (e.g., "강남역")
instead of the actual WALK start; update the logic in TransitResponseMapper so
departureLocation is taken from the first WALK in distanceSteps (use the first
distanceSteps element's startName) before the loop and keep using
createWalkStep(step, ChipType.WALK, departureLocation, isFirstWalk) and
isFirstWalk as-is so the initial WALK shows the correct origin.
In
@src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilteredResponse.java:
- Line 114: The call .targetGroups(notice.getTargetGroups()) can pass null;
change it to pass a non-null empty list instead (e.g., wrap
notice.getTargetGroups() with Optional.ofNullable(...).orElse(List.of()) or
Collections.emptyList()) so
NoticeDetailFilteredResponse.builder().targetGroups(...) always receives a safe
list; update imports if necessary and mirror the same null-handling pattern used
in NoticeService.
In
@src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilterRequest.java:
- Line 55: Update the Schema description to match the enum label change: change
the @Schema description on NoticeDetailFilterRequest from "정렬 유형 (거리순/생활태그 매칭순)"
to "정렬 유형 (거리순/주변환경 매칭순)"; ensure the INFRA enum constant label remains "주변환경
매칭순" and review the from() method in the same enum/class to confirm it still
supports expected Korean labels (note: clients sending the old label "생활태그 매칭순"
will now fail with IllegalArgumentException, so coordinate with frontend teams
about backward-compatibility).
In
@src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java:
- Line 196: Escape regex metacharacters in the county parameter before building
the regex to avoid unintended matches and ReDoS; in NoticeDocumentRepositoryImpl
replace the raw concatenation used in criteria.and("county").regex("^" + county)
with a version that wraps county via Pattern.quote(county) (import
java.util.regex.Pattern) so the regex becomes anchored but safe, and optionally
null-check county before quoting.
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/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilterRequest.java (1)
14-14: 문서 불일치: 스키마 설명 업데이트 필요스키마 설명에 "생활태그 매칭순"이 명시되어 있지만, 실제 enum 레이블(55 라인)은 "주변환경 매칭순"으로 변경되었습니다. API 문서의 정확성을 위해 설명도 함께 업데이트해야 합니다.
🔎 제안 수정 사항
-@Schema(description = "정렬 유형 (거리순/생활태그 매칭순)", example = "거리 순") +@Schema(description = "정렬 유형 (거리순/주변환경 매칭순)", example = "거리 순")
🧹 Nitpick comments (7)
src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/UnitTypeCompareResponse.java (1)
147-163: 만원 단위 변환 로직이 올바르게 구현되었습니다.정수 나눗셈으로 인한 소수점 이하 절삭이 발생하지만, 실제 보증금은 대부분 만원 단위의 배수이므로 문제없을 것으로 보입니다.
선택사항: 입력값 검증 추가
만원 단위가 아닌 보증금 값이 입력될 경우를 대비해 경고 로그를 추가할 수 있습니다:
long totalDeposit = deposit.getTotal(); +if (totalDeposit % 10000 != 0) { + log.warn("Deposit value {} is not a multiple of 10000, truncation will occur", totalDeposit); +} return CostInfo.builder() .totalDeposit(totalDeposit / 10000)src/main/java/com/pinHouse/server/platform/search/application/dto/FastUnitTypeResponse.java (1)
19-24: Schema 어노테이션 추가 승인, 단위 일관성 검토 필요보증금과 월 임대료에 대한 Swagger 문서화가 추가되어 API 명세가 명확해졌습니다. 다만 보증금은 만원 단위로, 월 임대료는 원 단위로 표시되어 단위 일관성이 부족합니다. 클라이언트에서 두 가지 단위를 혼용해야 하므로 혼란을 야기할 수 있습니다.
프론트엔드 팀과 협의하여 두 필드 모두 동일한 단위(만원 또는 원)로 통일하는 것을 권장합니
src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java (1)
181-184: 반복되는 todayStart 계산 로직 리팩토링 권장이 파일 내에서 동일한
todayStart계산 로직이 4번 반복됩니다 (lines 28-31, 106-109, 129-132, 181-184). 가독성과 유지보수성 향상을 위해 private 헬퍼 메서드로 추출하는 것을 권장합니다.🔎 헬퍼 메서드 추출 예시
클래스에 private 메서드 추가:
private Instant getTodayStartInSeoul(Instant now) { return ZonedDateTime.ofInstant(now, ZoneId.of("Asia/Seoul")) .toLocalDate() .atStartOfDay(ZoneId.of("Asia/Seoul")) .toInstant(); }사용 예시:
- Instant todayStart = ZonedDateTime.ofInstant(now, ZoneId.of("Asia/Seoul")) - .toLocalDate() - .atStartOfDay(ZoneId.of("Asia/Seoul")) - .toInstant(); + Instant todayStart = getTodayStartInSeoul(now);src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java (1)
30-30: 필드 네이밍 일관성 개선 권장필드명이
homeService이지만 타입은HomeUseCase입니다. 컨트롤러 레이어에서는 구현체가 아닌 인터페이스에 의존하는 것이 올바른 설계이므로, 필드명을homeUseCase로 변경하여 타입과 일치시키는 것을 권장합니다.🔎 네이밍 개선 제안
- private final HomeUseCase homeService; + private final HomeUseCase homeUseCase;그리고 메서드 내부에서도 수정:
- SliceResponse<NoticeListResponse> response = homeService.getDeadlineApproachingNotices( + SliceResponse<NoticeListResponse> response = homeUseCase.getDeadlineApproachingNotices(- SliceResponse<NoticeSearchResultResponse> response = homeService.searchNoticesIntegrated( + SliceResponse<NoticeSearchResultResponse> response = homeUseCase.searchNoticesIntegrated(src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java (1)
134-168: 광역 단위 추출 로직의 강건성 개선 권장하드코딩된 문자열 매칭 방식은 기능적으로는 작동하지만, 다음과 같은 경우에 문제가 될 수 있습니다:
- 행정구역 개편 시 유지보수 필요 (예: 충청남도 세종시 → 세종특별자치시 분리)
- "전북특별자치도"처럼 새로운 명칭 추가 시 대응 필요
- 주소 표기 변형 (예: "경기 성남시" vs "경기도 성남시")
한국 행정구역은 자주 변경되지 않으므로 현재 구현으로도 충분하지만, 장기적으로는 다음 중 하나를 고려해보세요:
Regionenum에aliases필드 추가하여 약칭 관리- 외부 주소 표준화 라이브러리 활용 (예: Juso API)
🔎 Region enum 개선 예시
public enum Region { SEOUL("서울특별시", List.of("서울")), GYEONGGI("경기도", List.of("경기")), JEONBUK("전북특별자치도", List.of("전북", "전라북도", "전라북")); private final String fullName; private final List<String> aliases; // 매칭 로직을 enum 내부로 이동 public static Region fromAddress(String address) { for (Region region : values()) { if (address.startsWith(region.fullName)) return region; for (String alias : region.aliases) { if (address.startsWith(alias)) return region; } } return null; } }src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java (2)
322-328: WALK 전용 경로에서 isFirstWalk 플래그의 의미론적 불일치WALK만 있는 경로에서
createWalkStep을 호출할 때isFirstWalk=false를 전달하고 있습니다. 첫 번째(그리고 유일한) WALK임에도 불구하고 false로 설정되어 있어 의미론적으로 일관성이 부족합니다.현재는
departureLocation=null이므로 실제 동작에는 영향이 없지만, 코드의 의도를 명확히 하기 위해isFirstWalk=true로 전달하는 것이 더 적절해 보입니다.🔎 제안하는 수정
- steps.add(createWalkStep(step, null, null, false)); + steps.add(createWalkStep(step, null, null, true));
309-411: 출발 단계 제거 및 첫 WALK 통합 리팩토링 방향성은 좋습니다별도의 DEPART 단계를 제거하고 첫 번째 WALK 단계에 출발지 정보를 통합한 접근 방식은 UX 측면에서 개선된 것으로 보입니다.
다만 출발지 정보의 정확성(Line 332)을 확인한 후, 프론트엔드 팀과 함께 응답 스펙(특히 첫 번째 WALK의 primaryText 형식)을 검증하는 것을 권장합니다.
추가 제안:
- Line 336의
isFirstWalk플래그 초기화와 Line 344의 플래그 업데이트 사이에 주석을 추가하여 의도를 명확히 하면 유지보수에 도움이 될 것 같습니다.🔎 주석 추가 예시
+ // 첫 번째 WALK 단계에만 출발지 정보를 포함하기 위한 플래그 boolean isFirstWalk = true; for (int i = 0; i < distanceSteps.size(); i++) { RootResult.DistanceStep step = distanceSteps.get(i); if (step.type() == RootResult.TransportType.WALK) { - // 첫 번째 WALK는 출발지 정보 포함 + // 첫 번째 WALK는 출발지 정보 포함, 이후부터는 일반 도보 텍스트만 표시 steps.add(createWalkStep(step, ChipType.WALK, departureLocation, isFirstWalk)); isFirstWalk = false;
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.javasrc/main/java/com/pinHouse/server/platform/home/application/usecase/HomeUseCase.javasrc/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.javasrc/main/java/com/pinHouse/server/platform/home/presentation/swagger/HomeApiSpec.javasrc/main/java/com/pinHouse/server/platform/housing/complex/application/dto/response/DepositMinMaxResponse.javasrc/main/java/com/pinHouse/server/platform/housing/complex/application/service/ComplexService.javasrc/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/dto/ComplexFilterResponse.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilterRequest.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilteredResponse.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailResponse.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/dto/UnitTypeCompareResponse.javasrc/main/java/com/pinHouse/server/platform/housing/notice/application/service/ComplexFilterService.javasrc/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryCustom.javasrc/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.javasrc/main/java/com/pinHouse/server/platform/like/application/dto/UnityTypeLikeResponse.javasrc/main/java/com/pinHouse/server/platform/search/application/dto/FastUnitTypeResponse.javasrc/main/java/com/pinHouse/server/platform/search/application/dto/NoticeSearchResultResponse.java
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java (2)
src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java (1)
RequiredArgsConstructor(18-208)src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java (1)
Slf4j(24-86)
🔇 Additional comments (26)
src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/ComplexFilterResponse.java (2)
73-77: 가격 분포 구간의 단위 변경이 정확히 구현되었습니다가격 구간 필드들이 일관되게 만원 단위로 변경되었으며, 구간 계산 로직도 올바르게 업데이트되었습니다.
ComplexFilterService의calculatePriceDistribution메서드에서 내부 계산은 원 단위로 수행한 후,PriceDistribution객체 생성 시/ 10000연산으로 만원 단위로 변환하고 있습니다. 두 경우 모두(빈 가격 목록과 정상 분포)에서 동일하게 적용되고 있어 구현이 일관성 있게 처리되었습니다.
54-61: 백엔드 구현에서 가격 단위 변환이 올바르게 적용됨 확인
ComplexFilterService.calculateCostFilter()메서드에서 원 단위로 계산된 모든 가격 값(minPrice, maxPrice, avgPrice)이 DTO 설정 전에 10000으로 나누어 만원 단위로 변환됩니다 (라인 411-413). 가격 분포(PriceDistribution)의 rangeStart, rangeEnd도 동일하게 변환됩니다 (라인 428-429, 458-459). 변환 로직이 명확하게 주석 처리되어 있으며 DTO의 Swagger 문서와 일치합니다.src/main/java/com/pinHouse/server/platform/like/application/dto/UnityTypeLikeResponse.java (2)
16-17: 단위 비대칭성 확인 필요
deposit은 만원 단위이지만monthlyRent는 원 단위를 사용하고 있습니다. 이러한 단위 불일치가 의도된 것인지 확인해 주세요. 클라이언트에서 두 필드를 함께 처리할 때 단위 변환 실수가 발생할 수 있습니다.
33-33: 불필요한 지적 - 현재 구현이 정확하고 일관성 있음이 코드는 검토가 불필요합니다. 다음과 같은 이유로 현재 구현이 정확합니다:
API 스펙 변경 아님:
deposit필드의 주석을 보면 "임대보증금 (만원 단위)"로 이미 명시되어 있습니다. 이는 API가 처음부터 만원 단위로 반환하도록 설계되었으므로 breaking change가 아닙니다.정밀도 손실 아님: 정수 나눗셈은 의도적인 단위 변환입니다. 원(Won) 단위에서 만원(10,000 Won) 단위로 변환하는 것이 목적이며, 이는 일관된 비즈니스 로직입니다.
공통 유틸 사용 불필요:
DepositMinMaxResponse.fromWon()은 최소/보통/최대 임대 옵션의 복합 객체를 처리하기 위해 설계되었습니다.UnityTypeLikeResponse는 단일 보증금 값만 필요하므로,long / 10000를 별도 유틸로 추출하는 것은 과도한 설계입니다. 현재의 인라인 나눗셈은 명확하고 간결합니다.같은 패턴이
FastUnitTypeResponse,UnitTypeCompareResponse등에서 일관되게 사용되고 있으며, 코드베이스 전체에서 일관성 있게 구현되어 있습니다.Likely an incorrect or invalid review comment.
src/main/java/com/pinHouse/server/platform/housing/notice/application/service/ComplexFilterService.java (2)
381-416: 가격 필터 계산 로직이 잘 구현되었습니다.원 단위로 계산 후 마지막에 만원 단위로 변환하는 접근 방식이 정확도를 유지하면서도 명확합니다. 주석도 변환 시점을 명확히 설명하고 있습니다.
421-465: 가격 분포 계산 로직이 올바르게 구현되었습니다.버킷 계산을 원 단위로 수행한 후 최종 결과만 만원 단위로 변환하여 정확도를 보장합니다. 엣지 케이스(빈 목록, 단일 가격)도 적절히 처리되고 있습니다.
src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/response/DepositMinMaxResponse.java (1)
18-28: 단위 변환을 위한 팩토리 메서드가 잘 설계되었습니다.
fromWon메서드는 보증금(만원)과 월세(원)의 단위 차이를 명확하게 처리하며, Javadoc 주석이 변환 로직을 잘 설명하고 있습니다. 이 방식은 중복 코드를 제거하고 단위 변환 로직을 한 곳에 집중시켜 유지보수성을 높입니다.
src/main/java/com/pinHouse/server/platform/housing/complex/application/service/ComplexService.java (1)
346-351:fromWon팩토리 메서드를 일관되게 사용하고 있습니다.세 가지 옵션(normal, min, max) 모두에서 새로운 팩토리 메서드를 사용하여 단위 변환 로직을 중앙화했습니다. 이는 코드 중복을 제거하고 향후 변환 로직 수정 시 한 곳만 변경하면 되도록 개선한 좋은 리팩토링입니다.
Also applies to: 381-386, 412-417
src/main/java/com/pinHouse/server/platform/home/application/usecase/HomeUseCase.java (1)
1-49: 홈 화면 Use Case 인터페이스가 잘 정의되었습니다.두 메서드 모두 명확한 책임을 가지고 있으며, Javadoc이 파라미터와 반환값을 상세히 설명하고 있습니다.
SliceResponse를 사용한 무한 스크롤 지원과 nullableuserId를 통한 비로그인 사용자 지원도 적절합니다.
src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/UnitTypeCompareResponse.java (1)
141-141: 유닛타입 비교 응답의 보증금 단위 표기가 기존 API 표준과 일치합니다.보증금 필드의 만원 단위 표기는 이 파일에서만의 변경이 아니며, FastUnitTypeResponse, ComplexFilterResponse, UnityTypeLikeResponse 등 기존 Response DTO들에서 이미 동일하게 적용되어 있습니다. 라인 160의 변환 로직(totalDeposit / 10000)도 일관성 있게 구현되어 있으므로, 별도의 추가 조정은 필요하지 않습니다.
src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilteredResponse.java (1)
100-103: 대상 계층 필드 추가 승인공고 기본 정보에 대상 계층(targetGroups) 필드가 추가되어 클라이언트에서 모집 대상 정보를 직접 활용할 수 있게 되었습니다. Schema 어노테이션도 적절하게 작성되었습니다.
src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailResponse.java (2)
34-35: 대상 계층 필드 추가 승인공고 상세 응답에 targetGroups 필드가 추가되어 NoticeDetailFilteredResponse와 일관된 응답 구조를 유지합니다.
57-57: targetGroups 매핑 로직 확인NoticeDetailFilteredResponse와 동일한 패턴으로 targetGroups를 매핑하고 있습니다. null 처리에 대한 검증은 NoticeDetailFilteredResponse 리뷰 코멘트를 참고하세요.
src/main/java/com/pinHouse/server/platform/search/application/dto/NoticeSearchResultResponse.java (3)
8-8: 필요한 import 추가 승인targetGroups 필드를 위한 List import가 올바르게 추가되었습니다.
40-41: 검색 결과에 대상 계층 필드 추가 승인공고 검색 결과에 targetGroups 필드가 추가되어 다른 공고 관련 DTO들(NoticeDetailResponse, NoticeDetailFilteredResponse)과 일관된 응답 구조를 제공합니다.
60-60: targetGroups 매핑 로직 승인from() 메서드에서 targetGroups를 올바르게 매핑하고 있으며, 오버로드된 from(NoticeDocument) 메서드가 from(notice, false)를 호출하여 일관성을 유지합니다. null 처리에 대한 검증은 NoticeDetailFilteredResponse 리뷰 코멘트를 참고하세요.
src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryCustom.java (1)
50-65: 마감임박 공고 조회 메서드 구현 확인 완료지역 기반 마감임박 공고 조회 메서드의 구현이 올바르게 작성되었습니다. NoticeDocumentRepositoryImpl.java에서 메서드가 정확하게 구현되어 있으며, HomeService와 정상적으로 통합되어 있습니다.
- 지역(city) 필터링: 정확한 일치 조회 ✓
- 시/군/구(county) 필터링: 정규식 접두어 매칭 ✓
- 활성 공고 필터링: applyEnd >= 오늘 시작 시간 ✓
- 마감임박순 정렬: HomeService에서 Pageable에 Sort 명시 ✓
- 페이징: PageImpl을 통한 올바른 구현 ✓
MongoDB 쿼리 로직도 적절하게 구성되어 있습니다.
src/main/java/com/pinHouse/server/platform/search/application/dto/FastUnitTypeResponse.java (1)
58-58: 정수 나눗셈으로 인한 절삭 발생 가능성보증금을 만원 단위로 변환할 때 정수 나눗셈(
/ 10000)을 사용하면 만원 미만 금액이 절삭됩니다. 예를 들어 5,555원의 경우 0만원으로 처리됩니다.현재 코드에는 보증금이 10,000의 배수임을 강제하는 검증이 없으며, 이 패턴이
FastUnitTypeResponse와UnityTypeLikeResponse등 여러 DTO에서 반복되고 있습니다. 일반적으로 보증금은 만원 단위로 책정되므로 실무상 문제가 없을 수 있으나, 다음 중 하나를 확인할 필요가 있습니다:
- 실제 데이터베이스에 만원 미만의 보증금 데이터가 존재하는지 확인
- 또는 Deposit 클래스에 검증 제약조건(
@Min,@PositiveLong등)을 추가하여 설계 의도를 명시화
src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java (2)
37-56: 구현이 올바릅니다
@CheckLogin애노테이션을 통한 인증 확인,PrincipalDetails에서 userId 추출, 그리고 use case로의 적절한 위임이 잘 구현되어 있습니다.
62-85: 선택적 인증 처리가 올바릅니다
principalDetails의 null 체크를 통해 비로그인 사용자도 검색 API를 사용할 수 있도록 처리한 부분이 좋습니다. 로그인한 사용자에게는 좋아요 정보를 제공하고, 비로그인 사용자에게는 기본 검색 결과만 제공하는 설계가 적절합니다.
src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java (3)
54-100: 마감임박 공고 조회 로직이 올바르게 구현되었습니다PinPoint 소유권 검증, 지역/시군구 추출, 페이지네이션, 좋아요 상태 포함 등 핵심 로직이 잘 구현되어 있습니다. 특히 사용자 소유권 검증을 통해 다른 사용자의 PinPoint에 접근하는 것을 방지한 점이 좋습니다.
175-184: 통합 검색 위임 로직이 적절합니다기존
NoticeSearchUseCase를 재사용하여 중복을 제거한 점이 좋습니다. 홈 화면의 통합 검색이 별도의 복잡한 로직 없이 기존 검색 기능을 그대로 활용하는 설계가 합리적입니다.
107-127: county 추출 로직은 의도된 대로 동작 중입니다
extractCountyFromAddress메서드의 4가지 테스트 케이스 모두 올바르게 처리됩니다:
- "서울특별시 강남구" → "강남구" ✓
- "서울 강남구" → "강남구" ✓
- "세종특별자치도" → null ✓
- "제주특별자치도 제주시" → "제주시" ✓
인덱스 1부터 순회하여 첫 번째 토큰을 건너뛰는 방식은 광역 단위를 제외한 시/군/구를 추출하기 위한 올바른 설계입니다. 세종특별자치도처럼 기초자치단체 단계가 없는 지역에서 null을 반환하는 것은 의도된 동작이며, 저장소의 쿼리 메서드가 null인 경우 county 필터링을 건너뛰므로(NoticeDocumentRepositoryImpl 195-196줄) 추가 처리 없이 올바르게 작동합니다.
src/main/java/com/pinHouse/server/platform/home/presentation/swagger/HomeApiSpec.java (1)
17-56: API 명세가 명확하게 작성되었습니다Swagger 애노테이션이 잘 작성되어 있습니다:
- 각 엔드포인트의 기능과 제약사항이 명확히 문서화됨
- 파라미터 설명과 예시가 적절함
- 인증 요구사항이 명시됨 (마감임박 공고는 로그인 필수, 통합 검색은 선택)
프론트엔드 팀이 이 명세를 바탕으로 API를 정확히 이해하고 통합할 수 있을 것입니다.
src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java (2)
360-376: LGTM! 단계 필터링 로직이 올바릅니다0분 소요 구간을 제거하면서 ARRIVE/ALIGHT 단계(minutes=null)는 유지하는 필터링 로직이 정확합니다. 사용자 경험 측면에서도 적절한 처리입니다.
384-411: createWalkStep 구현은 일관되지만, 출발지 데이터 소스 확인 필요메서드 구현 자체는 깔끔하고 null 처리도 적절합니다. 다만 Line 332에서 지적한 것처럼,
departureLocation파라미터로 전달되는 값이 의미론적으로 올바른지 확인이 필요합니다.Line 404의
stopName과 Line 405의primaryText를 분리한 것은 좋은 설계입니다. 내부 로직용 데이터와 사용자 표시용 텍스트를 명확히 구분하고 있습니다.
| NoticeListRequest.Region region = extractRegionFromAddress(pinPoint.getAddress()); | ||
| String county = extractCountyFromAddress(pinPoint.getAddress()); |
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.
정규식 특수 문자 이스케이프 필요
추출된 county 값이 NoticeDocumentRepositoryImpl의 line 196에서 정규식 패턴에 직접 사용됩니다. 만약 주소에서 추출된 county에 정규식 메타 문자가 포함되어 있으면 의도하지 않은 매칭이나 ReDoS 취약점이 발생할 수 있습니다.
Repository 레이어에서 Pattern.quote()를 사용하여 이스케이프 처리하는 것을 권장합니다 (NoticeDocumentRepositoryImpl.java의 다른 리뷰 코멘트 참조).
🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java
around lines 70-71, The extracted county string from
extractCountyFromAddress(pinPoint.getAddress()) may contain regex metacharacters
and is later used in a Pattern in NoticeDocumentRepositoryImpl; escape it before
usage by quoting the value with Pattern.quote() (either wrap the county here in
HomeService before passing to the repository or update the repository method
that consumes the county to call Pattern.quote(county) when building the
regex/Pattern) so no unescaped user/address data is interpolated into regexes.
|
|
||
| // 페이징 설정 (마감임박순 정렬) | ||
| Sort sort = Sort.by(Sort.Order.asc("applyEnd"), Sort.Order.asc("noticeId")); | ||
| Pageable pageable = PageRequest.of(sliceRequest.page() - 1, sliceRequest.offSet(), sort); |
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 SliceRequest file
fd -t f -i slicerequest.javaRepository: PinHouse/PinHouse_BE
Length of output: 147
🏁 Script executed:
cat -n src/main/java/com/pinHouse/server/core/response/response/pageable/SliceRequest.javaRepository: PinHouse/PinHouse_BE
Length of output: 597
🏁 Script executed:
# Check HomeService line 78 with context
sed -n '70,85p' src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.javaRepository: PinHouse/PinHouse_BE
Length of output: 788
🏁 Script executed:
# Search for tests related to HomeService or SliceRequest
find . -type f -name "*HomeServiceTest*" -o -name "*SliceRequestTest*" 2>/dev/null | head -20
# Also search for other usages of SliceRequest
rg "SliceRequest" --type java -B 2 -A 2 | head -100Repository: PinHouse/PinHouse_BE
Length of output: 11714
🏁 Script executed:
# Check HomeApi to see how SliceRequest is documented
cat -n src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java | head -80Repository: PinHouse/PinHouse_BE
Length of output: 3702
🏁 Script executed:
# Check HomeApiSpec for any additional documentation
cat -n src/main/java/com/pinHouse/server/platform/home/presentation/swagger/HomeApiSpec.java | head -120Repository: PinHouse/PinHouse_BE
Length of output: 3188
API 문서와 코드 로직의 페이징 규칙 불일치 해결 필수
SliceRequest의 Swagger 문서에서는 "페이지 번호 (0부터 시작)"이라고 명시했으나, 실제 코드는 sliceRequest.page() - 1로 1-based 입력을 가정하고 있습니다. 이로 인한 문제점:
- 문서 오류: SliceRequest 스키마에서 "0부터 시작"이라고 했으나, HomeApi 주석과 코드 로직은 page=1을 첫 번째 페이지로 취급
- 검증 부재: SliceRequest에
@Min(1)등의 검증이 없어서, 클라이언트가 page=0을 전송하면-1이 되어PageRequest.of()에서 오류 발생 - 클라이언트 혼란: API 문서를 신뢰하고 page=0을 보내면 실패함
해결책:
- SliceRequest의 Swagger 문서를 "페이지 번호 (1부터 시작)"으로 수정
- page 필드에
@Min(value = 1)검증 추가
🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java
around line 78, The code treats SliceRequest.page() as 1-based (HomeService uses
PageRequest.of(sliceRequest.page() - 1, ...)) while the Swagger schema says
pages are 0-based; fix by making the API contract 1-based and enforcing it:
update the SliceRequest Swagger/Schema description to say "페이지 번호 (1부터 시작)" and
add validation on the page field (e.g., @Min(1)) in the SliceRequest class so
clients cannot pass 0 or negative values that would cause PageRequest.of() to
receive a negative index; keep the existing subtraction in HomeService
(PageRequest.of(sliceRequest.page() - 1, ...)) so existing logic remains correct
once validation and docs are aligned.
| // 출발지 정보 (첫 번째 WALK에 사용) | ||
| RootResult.DistanceStep firstTransport = transportSteps.get(0); | ||
| steps.add(createDepartStep(firstTransport.startName())); | ||
| String departureLocation = firstTransport.startName(); | ||
|
|
||
| // 2. 전체 구간 순회하며 steps 생성 | ||
| // 전체 구간 순회하며 steps 생성 | ||
| int transportIndex = 0; | ||
| boolean isFirstWalk = true; | ||
|
|
||
| for (int i = 0; i < distanceSteps.size(); i++) { | ||
| RootResult.DistanceStep step = distanceSteps.get(i); | ||
|
|
||
| if (step.type() == RootResult.TransportType.WALK) { | ||
| // WALK step 추가 (색상 포함) | ||
| steps.add(createWalkStep(step, ChipType.WALK)); | ||
| // 첫 번째 WALK는 출발지 정보 포함 | ||
| steps.add(createWalkStep(step, ChipType.WALK, departureLocation, isFirstWalk)); | ||
| isFirstWalk = false; |
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:
cd src/main/java/com/pinHouse/server/platform/housing/complex/application/util && \
rg -n "class DistanceStep|record DistanceStep" -A 20Repository: PinHouse/PinHouse_BE
Length of output: 46
🏁 Script executed:
rg -n "startName|endName" src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java -B 2 -A 2Repository: PinHouse/PinHouse_BE
Length of output: 1440
🏁 Script executed:
# Let's also look at the complete TransitResponseMapper file to understand the context better
wc -l src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.javaRepository: PinHouse/PinHouse_BE
Length of output: 170
🏁 Script executed:
# Search for RootResult class to understand DistanceStep structure
rg -n "class RootResult|record RootResult" -A 5Repository: PinHouse/PinHouse_BE
Length of output: 847
🏁 Script executed:
# Check how the transportation steps are being used
rg -n "transportSteps|distanceSteps" src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java -B 1 -A 1Repository: PinHouse/PinHouse_BE
Length of output: 1500
🏁 Script executed:
rg -n "record DistanceStep|class DistanceStep" -A 20 src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/RootResult.javaRepository: PinHouse/PinHouse_BE
Length of output: 797
🏁 Script executed:
# Look at createWalkStep method implementation
rg -n "createWalkStep" src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java -A 15Repository: PinHouse/PinHouse_BE
Length of output: 2104
🏁 Script executed:
# Look at the entire logic block around lines 330-350 to understand the flow
sed -n '320,365p' src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.javaRepository: PinHouse/PinHouse_BE
Length of output: 1901
🏁 Script executed:
# Search for how DistanceStep is created to understand what startName/endName represent
rg -n "DistanceStep" src/main/java/com/pinHouse/server/platform/housing/complex/application/dto/result/RootResult.java -B 2 -A 10Repository: PinHouse/PinHouse_BE
Length of output: 2311
첫 번째 WALK의 출발지가 잘못 설정됨
Line 332에서 departureLocation을 firstTransport.startName()에서 추출하고 있는데, 이는 의미론적으로 오류입니다.
예시 경로: [WALK: 출발지 → 강남역] → [BUS: 강남역 → 역삼역] → [WALK: 역삼역 → 도착지]
현재 로직:
firstTransport= BUS (강남역 → 역삼역)departureLocation=firstTransport.startName()= "강남역"- 결과 텍스트: "강남역에서 도보 약 n분"
하지만 첫 번째 WALK는 강남역으로 걸어가는 것이지, 강남역에서 걸어가는 것이 아닙니다.
출발지는 distanceSteps의 첫 번째 WALK 단계의 startName을 사용해야 합니다
// 현재 (잘못된 것)
RootResult.DistanceStep firstTransport = transportSteps.get(0);
String departureLocation = firstTransport.startName();
// 수정 필요 (올바른 것)
RootResult.DistanceStep firstWalk = distanceSteps.get(0);
String departureLocation = firstWalk.startName();🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/housing/complex/application/util/TransitResponseMapper.java
around lines 330-344, The departureLocation for the first WALK is being derived
from firstTransport/transportSteps which yields the wrong start (e.g., "강남역")
instead of the actual WALK start; update the logic in TransitResponseMapper so
departureLocation is taken from the first WALK in distanceSteps (use the first
distanceSteps element's startName) before the loop and keep using
createWalkStep(step, ChipType.WALK, departureLocation, isFirstWalk) and
isFirstWalk as-is so the initial WALK shows the correct origin.
| .period(period) | ||
| .type(notice.getSupplyType()) | ||
| .housingType(notice.getHouseType()) | ||
| .targetGroups(notice.getTargetGroups()) |
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 and examine NoticeDocument class
find . -type f -name "NoticeDocument.java" | head -20Repository: PinHouse/PinHouse_BE
Length of output: 157
🏁 Script executed:
# Search for targetGroups field and getter in Java files
rg -n "getTargetGroups|targetGroups" --type java -C 3 | head -100Repository: PinHouse/PinHouse_BE
Length of output: 7714
🏁 Script executed:
# Examine the NoticeDetailFilteredResponse to understand the field definition
cat -n src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilteredResponse.java | head -150Repository: PinHouse/PinHouse_BE
Length of output: 6566
🏁 Script executed:
cat -n ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/NoticeDocument.java | tail -50Repository: PinHouse/PinHouse_BE
Length of output: 1303
🏁 Script executed:
# Check for the full NoticeDocument class including getTargetGroups() method
cat ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/NoticeDocument.javaRepository: PinHouse/PinHouse_BE
Length of output: 1471
🏁 Script executed:
# Check for @NotNull or similar annotations on the field
rg -n "@NotNull|@Nullable" ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/entity/NoticeDocument.javaRepository: PinHouse/PinHouse_BE
Length of output: 46
targetGroups의 null 처리 필수
notice.getTargetGroups()는 null을 반환할 수 있습니다. 코드베이스의 NoticeService에서 Optional.ofNullable(n.getTargetGroups()).orElse(List.of())로 null을 처리하는 패턴이 이미 존재하므로, 이 부분도 동일하게 null 안전성을 보장해야 합니다. 빈 리스트로 대체하여 클라이언트에서 null 체크 없이 안전하게 사용할 수 있도록 수정하세요.
🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilteredResponse.java
around line 114, The call .targetGroups(notice.getTargetGroups()) can pass null;
change it to pass a non-null empty list instead (e.g., wrap
notice.getTargetGroups() with Optional.ofNullable(...).orElse(List.of()) or
Collections.emptyList()) so
NoticeDetailFilteredResponse.builder().targetGroups(...) always receives a safe
list; update imports if necessary and mirror the same null-handling pattern used
in NoticeService.
|
|
||
| KM("거리 순"), | ||
| INFRA("생활태그 매칭순"); | ||
| INFRA("주변환경 매칭순"); |
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:
cat -n src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilterRequest.javaRepository: PinHouse/PinHouse_BE
Length of output: 3416
🏁 Script executed:
# Search for references to the old label "생활태그 매칭순" in the codebase
rg -n "생활태그\s*매칭순" --type java -C 3
# Also search for the new label "주변환경 매칭순" to see if it's referenced elsewhere
rg -n "주변환경\s*매칭순" --type java -C 3Repository: PinHouse/PinHouse_BE
Length of output: 1985
🏁 Script executed:
# Search for usages of DetailSortType enum to understand API impact
rg -n "DetailSortType" --type java -C 2 | head -50Repository: PinHouse/PinHouse_BE
Length of output: 4479
🏁 Script executed:
# Check if there are any test files or documentation that reference the sort types
rg -n "거리\s*순|거리순" --type java -C 2 | head -50Repository: PinHouse/PinHouse_BE
Length of output: 1376
🏁 Script executed:
# Check if from() method is called from request parameter binding or JSON deserialization
rg -n "from\(" --type java -B 2 -A 2 | grep -i "sorttype\|detailsort" | head -20Repository: PinHouse/PinHouse_BE
Length of output: 2567
🏁 Script executed:
# Search for any test cases that show how the enum is used
fd -e "Test\.java" -o -e "Test\.kt" | xargs grep -l "DetailSortType\|INFRA" 2>/dev/null | head -5Repository: PinHouse/PinHouse_BE
Length of output: 180
🏁 Script executed:
# Search for converter or handler related to DetailSortType
rg -n "StringToDetailSortTypeConverter\|DetailSortType" --type java | head -30Repository: PinHouse/PinHouse_BE
Length of output: 46
🏁 Script executed:
# Check if there's a Spring converter for DetailSortType
fd "StringTo.*DetailSortType.*\.java" -o "DetailSortType.*Converter.*\.java"Repository: PinHouse/PinHouse_BE
Length of output: 249
🏁 Script executed:
# Search in test directory for DetailSortType usage
find . -name "*Test.java" -o -name "*Tests.java" | xargs grep -l "DetailSortType\|INFRA" 2>/dev/null | head -5Repository: PinHouse/PinHouse_BE
Length of output: 46
🏁 Script executed:
# Search for actual usage of DetailSortType in service/controller layers
rg -n "DetailSortType\|sortType" src/main/java --type java | grep -v "DetailSortType.java" | head -40Repository: PinHouse/PinHouse_BE
Length of output: 46
🏁 Script executed:
# Check if there's any reference to the old label in frontend, docs, or tests
rg -n "생활태그.*매칭순" . --type-list all 2>/dev/null | head -20Repository: PinHouse/PinHouse_BE
Length of output: 46
스키마 설명 업데이트 필요: 라벨 변경이 문서에 반영되지 않음
14번 라인의 @Schema 설명이 실제 enum 값과 일치하지 않습니다:
- 문서: "정렬 유형 (거리순/생활태그 매칭순)"
- 실제: INFRA 라벨은 "주변환경 매칭순"으로 변경됨
14번 라인의 스키마 설명을 "정렬 유형 (거리순/주변환경 매칭순)"으로 수정하세요.
추가 고려사항: from() 메서드(65-78번 라인)가 한글 라벨 매칭을 지원하므로, 외부 클라이언트가 이전 라벨 "생활태그 매칭순"을 직렬화 역변환할 경우 IllegalArgumentException이 발생합니다. 프론트엔드 팀과 변경 사항을 조율하여 클라이언트 영향을 확인하세요.
🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/housing/notice/application/dto/NoticeDetailFilterRequest.java
around line 55, Update the Schema description to match the enum label change:
change the @Schema description on NoticeDetailFilterRequest from "정렬 유형
(거리순/생활태그 매칭순)" to "정렬 유형 (거리순/주변환경 매칭순)"; ensure the INFRA enum constant label
remains "주변환경 매칭순" and review the from() method in the same enum/class to
confirm it still supports expected Korean labels (note: clients sending the old
label "생활태그 매칭순" will now fail with IllegalArgumentException, so coordinate with
frontend teams about backward-compatibility).
|
|
||
| /// 시/군/구 필터링 (부분 일치: "성남시"로 "성남시 수정구", "성남시 분당구" 등 매칭) | ||
| if (county != null && !county.isBlank()) { | ||
| criteria.and("county").regex("^" + county); |
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.
정규식 특수 문자 이스케이프 필요
county 파라미터가 정규식 패턴에 직접 연결되고 있습니다. 만약 county에 정규식 메타 문자(., *, +, ?, [, ] 등)가 포함되어 있으면:
- 의도하지 않은 매칭이 발생할 수 있습니다
- ReDoS(Regular Expression Denial of Service) 공격에 취약할 수 있습니다
예를 들어, county가 "성남.시"인 경우 "성남X시"도 매칭됩니다.
🔎 정규식 이스케이프 적용 방안
/// 시/군/구 필터링 (부분 일치: "성남시"로 "성남시 수정구", "성남시 분당구" 등 매칭)
if (county != null && !county.isBlank()) {
- criteria.and("county").regex("^" + county);
+ criteria.and("county").regex("^" + Pattern.quote(county));
}필요한 import 추가:
+import java.util.regex.Pattern;📝 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.
| criteria.and("county").regex("^" + county); | |
| criteria.and("county").regex("^" + Pattern.quote(county)); |
🤖 Prompt for AI Agents
In
@src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java
around line 196, Escape regex metacharacters in the county parameter before
building the regex to avoid unintended matches and ReDoS; in
NoticeDocumentRepositoryImpl replace the raw concatenation used in
criteria.and("county").regex("^" + county) with a version that wraps county via
Pattern.quote(county) (import java.util.regex.Pattern) so the regex becomes
anchored but safe, and optionally null-check county before quoting.
📌 작업한 내용
🔍 참고 사항
🖼️ 스크린샷
🔗 관련 이슈
✅ 체크리스트
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선사항
✏️ Tip: You can customize this high-level summary in your review settings.