Skip to content

Conversation

@doup2001
Copy link
Member

@doup2001 doup2001 commented Jan 17, 2026

📌 작업한 내용

🔍 참고 사항

🖼️ 스크린샷

🔗 관련 이슈

✅ 체크리스트

  • 로컬에서 빌드 및 테스트 완료
  • 코드 리뷰 반영 완료
  • 문서화 필요 여부 확인

Summary by CodeRabbit

릴리즈 노트

  • New Features

    • 진단 결과 기반 부동산 추천 기능 추가
    • 홈 검색에 카테고리별 검색 기능 추가 (공고, 단지, 대상층, 지역, 주택유형)
    • 인기 검색어 조회 기능 추가
    • 홈 검색 통합 미리보기 기능 추가
    • 프로필 이미지 업로드를 위한 Presigned URL 생성 기능 추가
  • Bug Fixes

    • 진단 기록 미발견 시 에러 처리 개선

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

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

Test Results

22 tests  ±0   22 ✅ ±0   0s ⏱️ ±0s
 3 suites ±0    0 💤 ±0 
 3 files   ±0    0 ❌ ±0 

Results for commit 572df1f. ± Comparison against base commit cd95f3b.

♻️ This comment has been updated with latest results.

@coderabbitai
Copy link

coderabbitai bot commented Jan 18, 2026

개요

AWS S3 통합을 통한 이미지 업로드 기능, 진단 기반 추천을 그룹화하는 V2 진단 API, 카테고리 필터링과 진단 기반 추천을 포함한 확장된 홈 검색 기능, 그리고 검색 키워드 범위(scope) 관리를 추가합니다.

변경 사항

일관된 변경 / 파일 요약
S3 설정 및 AWS 통합
build.gradle, src/main/java/com/pinHouse/server/core/config/S3Config.java
AWS SDK v2 BOM 플랫폼 정렬 및 S3, Auth 의존성 추가. S3Client 빈 설정으로 리전, 액세스 키, 시크릿 키 주입
진단 에러 코드 확장
src/main/java/com/pinHouse/server/core/exception/code/DiagnosisErrorCode.java
NOT_FOUND_DIAGNOSIS(404_001) 열거형 상수 추가
이미지 에러 코드 및 타입
src/main/java/com/pinHouse/server/core/exception/code/ImageErrorCode.java, src/main/java/com/pinHouse/server/platform/image/domain/entity/ImageType.java
ImageErrorCode 열거형 추가(400, 500 에러 포함), PROFILE 경로를 갖춘 ImageType 열거형 정의
진단 V2 API 및 DTO
src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/dto/DiagnosisRecommendationGroup.java, src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/dto/DiagnosisResponseV2.java, src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/service/DiagnosisService.java, src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/usecase/DiagnosisUseCase.java, src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/presentation/DiagnosisApi.java, src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/presentation/swagger/DiagnosisApiSpec.java
DiagnosisRecommendationGroup 및 DiagnosisResponseV2 레코드 생성, /v2/diagnosis 엔드포인트 추가(diagnosisV2, getLatestDiagnosisV2), notice type별로 supply type 그룹화
홈 검색 DTO 및 타입
src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchCategoryType.java, src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchCategoryPageResponse.java, src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchSectionResponse.java, src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchOverviewResponse.java, src/main/java/com/pinHouse/server/platform/home/application/dto/HomeNoticeListResponse.java
HomeSearchCategoryType 열거형(NOTICE, COMPLEX, TARGET_GROUP, REGION, HOUSE_TYPE), 페이징 응답 DTO 및 개요 응답 추가, 홈 공고 목록 응답에 title 필드 추가
홈 검색 서비스 및 API
src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java, src/main/java/com/pinHouse/server/platform/home/application/usecase/HomeUseCase.java, src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java, src/main/java/com/pinHouse/server/platform/home/presentation/swagger/HomeApiSpec.java
searchNoticesIntegrated를 searchHomeByCategory로 변경, searchHomeOverview, getHomePopularKeywords, getRecommendedNoticesByDiagnosis 추가, /search/overview, /search/category, /search/popular, /recommended-notices 엔드포인트 신규
이미지 업로드 및 Presigned URL
src/main/java/com/pinHouse/server/platform/image/application/dto/PresignedUrlRequest.java, src/main/java/com/pinHouse/server/platform/image/application/dto/PresignedUrlResponse.java, src/main/java/com/pinHouse/server/platform/image/application/service/ImageService.java, src/main/java/com/pinHouse/server/platform/image/application/usecase/ImageUseCase.java, src/main/java/com/pinHouse/server/platform/image/external/PresignedUrlGenerator.java, src/main/java/com/pinHouse/server/platform/image/external/S3PresignedUrlGenerator.java, src/main/java/com/pinHouse/server/platform/image/presentation/ImageApi.java, src/main/java/com/pinHouse/server/platform/image/presentation/swagger/ImageApiSpec.java
PresignedUrlRequest/Response DTO, ImageUseCase 인터페이스, ImageService 구현, S3PresignedUrlGenerator 구현, /v1/images/presigned-url 엔드포인트 추가(jpeg, jpg, png, gif 검증 포함)
저장소 검색 및 추천 기능
src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepository.java, src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepositoryImpl.java, src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryCustom.java, src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java
searchByName(keyword, pageable) 추가, findRecommendedNoticesByDiagnosis, searchTargetGroups, searchRegions, searchHouseTypes 등 필터링 메서드 추가
검색 키워드 범위 관리
src/main/java/com/pinHouse/server/platform/search/domain/entity/SearchKeywordScope.java, src/main/java/com/pinHouse/server/platform/search/domain/entity/SearchKeyword.java, src/main/java/com/pinHouse/server/platform/search/domain/repository/SearchKeywordRepository.java, src/main/java/com/pinHouse/server/platform/search/application/service/SearchKeywordService.java, src/main/java/com/pinHouse/server/platform/search/application/usecase/SearchKeywordUseCase.java
SearchKeywordScope 열거형(GENERAL, HOME) 추가, SearchKeyword에 scope 필드 및 복합 인덱스 추가, 저장소 메서드를 scope 기반으로 업데이트(findByKeywordAndScope, findAllByScopeOrderByCountDescLastSearchedAtDesc)

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant ImageApi
    participant ImageService
    participant S3PresignedUrlGenerator
    participant S3Client
    participant AWS S3

    Client->>ImageApi: POST /v1/images/presigned-url<br/>PresignedUrlRequest
    ImageApi->>ImageService: generatePresignedUrl(request, userId)
    ImageService->>ImageService: validateContentType(contentType)
    ImageService->>ImageService: extractAndValidateExtension(fileName)
    ImageService->>ImageService: generateObjectKey(userId, extension)
    ImageService->>S3PresignedUrlGenerator: generatePutPresignedUrl(objectKey, contentType)
    S3PresignedUrlGenerator->>S3Client: getRegion()
    S3PresignedUrlGenerator->>S3PresignedUrlGenerator: buildS3Presigner()
    S3PresignedUrlGenerator->>AWS S3: generatePresignedPutObjectRequest()
    AWS S3-->>S3PresignedUrlGenerator: presignedUrl
    S3PresignedUrlGenerator->>S3PresignedUrlGenerator: getPublicUrl(objectKey)
    S3PresignedUrlGenerator-->>ImageService: PresignedUrlResponse
    ImageService-->>ImageApi: PresignedUrlResponse
    ImageApi-->>Client: ApiResponse<PresignedUrlResponse><br/>presignedUrl, imageUrl, expiresIn
Loading
sequenceDiagram
    participant Client
    participant DiagnosisApi
    participant DiagnosisService
    participant EvaluationContext
    participant DiagnosisResponseV2
    participant DiagnosisRecommendationGroup

    Client->>DiagnosisApi: POST /v2/diagnosis<br/>DiagnosisRequest
    DiagnosisApi->>DiagnosisService: diagnoseV2(userId, request)
    DiagnosisService->>DiagnosisService: loadUser(userId)
    DiagnosisService->>DiagnosisService: createAndSave(Diagnosis)
    DiagnosisService->>EvaluationContext: evaluateRules()
    DiagnosisService->>DiagnosisResponseV2: from(EvaluationContext)
    DiagnosisResponseV2->>DiagnosisResponseV2: getCurrentCandidates()
    alt No Candidates
        DiagnosisResponseV2-->>DiagnosisResponseV2: eligible=false, empty list
    else Has Candidates
        DiagnosisResponseV2->>DiagnosisResponseV2: groupByNoticeType()
        DiagnosisResponseV2->>DiagnosisResponseV2: groupBySupplyType(deduped)
        DiagnosisResponseV2->>DiagnosisRecommendationGroup: create(noticeType, supplyTypes)
        DiagnosisRecommendationGroup-->>DiagnosisResponseV2: DiagnosisRecommendationGroup
        DiagnosisResponseV2-->>DiagnosisResponseV2: eligible=true, recommended list
    end
    DiagnosisResponseV2-->>DiagnosisService: DiagnosisResponseV2
    DiagnosisService-->>DiagnosisApi: DiagnosisResponseV2
    DiagnosisApi-->>Client: ApiResponse<DiagnosisResponseV2>
Loading
sequenceDiagram
    participant Client
    participant HomeApi
    participant HomeService
    participant NoticeDocumentRepository
    participant DiagnosisService
    participant SliceResponse

    Client->>HomeApi: GET /v1/homes/recommended-notices<br/>SliceRequest, userId
    HomeApi->>HomeService: getRecommendedNoticesByDiagnosis(sliceRequest, userId)
    HomeService->>DiagnosisService: getDiagnoseSummaryV2(userId)
    DiagnosisService-->>HomeService: DiagnosisResponseV2
    alt No Diagnosis Found
        HomeService-->>HomeApi: 404 Error
    else Diagnosis Exists
        HomeService->>HomeService: extractSupplyTypes()
        HomeService->>NoticeDocumentRepository: findRecommendedNoticesByDiagnosis(supplyTypes, pageable)
        NoticeDocumentRepository-->>HomeService: Page<NoticeDocument>
        HomeService->>HomeService: buildAddress(), mapToDto()
        HomeService->>HomeService: likedIds(userId)
        HomeService->>SliceResponse: wrapResponse(notices, hasNext)
        SliceResponse-->>HomeService: HomeNoticeListResponse
        HomeService-->>HomeApi: HomeNoticeListResponse
    end
    HomeApi-->>Client: ApiResponse<HomeNoticeListResponse>
Loading

예상 코드 리뷰 노력

🎯 4 (복잡) | ⏱️ ~45분

🐰 S3로 사진을 올려두고,
진단으로 집을 추천해주고,
키워드로 검색하면 카테고리 쏙쏙!
홈 화면은 풍요로워졌네,
코드의 흐름도 깔끔하게 ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명은 템플릿 구조만 있고 모든 섹션이 비어있습니다. 작업한 내용, 참고 사항, 관련 이슈 등이 작성되지 않았으며 체크리스트도 체크되지 않았습니다. 각 섹션을 구체적으로 작성해주세요. 작업한 내용에 S3, 이미지 업로드, 진단 기반 추천, 홈 검색 기능 등을 설명하고, 관련 이슈를 링크하며, 체크리스트를 완료하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 58.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 '이미지 관련 기능 구현'으로 변경사항의 주요 내용인 S3 기반 이미지 업로드(Presigned URL), 이미지 관련 DTO, 그리고 진단 기반 추천 기능을 부분적으로 반영합니다.

✏️ 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: 10

Caution

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

⚠️ Outside diff range comments (2)
src/main/java/com/pinHouse/server/platform/search/application/service/SearchKeywordService.java (1)

44-90: scope null 방어 추가가 필요합니다.
외부 호출에서 null이 들어오면 null 스코프로 기록/조회되어 통계 누락이 발생할 수 있습니다. 기본값 처리 또는 명시적 예외가 필요합니다.

🔧 제안 수정안
 public void recordSearch(String keyword, SearchKeywordScope scope) {
+    if (scope == null) {
+        scope = SearchKeywordScope.GENERAL;
+    }
     if (keyword == null || keyword.trim().isEmpty()) {
         return;
     }
@@
 public List<PopularKeywordResponse> getPopularKeywords(int limit, SearchKeywordScope scope) {
+    if (scope == null) {
+        scope = SearchKeywordScope.GENERAL;
+    }
     Pageable pageable = PageRequest.of(0, limit);
     List<SearchKeyword> keywords = repository.findAllByScopeOrderByCountDescLastSearchedAtDesc(scope, pageable);
     return PopularKeywordResponse.from(keywords);
 }
src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/presentation/DiagnosisApi.java (1)

17-79: 경로 기반 버전과 쿼리 파라미터 혼용으로 인한 매핑 오류 및 모호성 문제

클래스 레벨 매핑이 /v1/diagnosis/v2/diagnosis를 모두 포함하면서 메서드 수준에서는 params="v=2" 조건으로만 v2를 구분하는 구조는 다음과 같은 문제를 야기합니다:

  1. 의도하지 않은 라우팅: POST /v2/diagnosis 요청이 v=2 파라미터 없이 들어오면 v1 메서드(diagnosis())로 매칭되어 v1 응답을 반환합니다.
  2. 모호한 매핑: POST /v2/diagnosis?v=2 요청은 v=2 파라미터 조건이 없는 v1 메서드와 params="v=2" 조건의 v2 메서드 모두와 매칭되어 Spring이 시작 시 IllegalStateException을 발생시킵니다.

권장: 경로 기반 버전(/v1, /v2)을 선택하면 메서드별 params 조건을 제거하고 별도 컨트롤러로 분리하거나, 쿼리 파라미터 기반 버전(/diagnosis?v=1 vs ?v=2)으로 통일하는 것이 안전합니다.

🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 52-55: Update the AWS SDK BOM dependency version used for S3/auth
from 'software.amazon.awssdk:bom:2.21.0' to at least 2.33.x to remediate known
CVEs; in the build.gradle block that declares implementation
platform('software.amazon.awssdk:bom:2.21.0') (alongside implementation
'software.amazon.awssdk:s3' and implementation 'software.amazon.awssdk:auth'),
bump the bom coordinate to a 2.33.x+ release and then re-run your dependency
vulnerability scanners (OWASP Dependency-Check, Snyk, Dependabot/GitHub) to
verify all transitive issues are resolved and compatible with Spring Boot 3.4.3.

In `@src/main/java/com/pinHouse/server/core/exception/code/ImageErrorCode.java`:
- Line 20: Remove the unused enum constant FILE_SIZE_EXCEEDED from
ImageErrorCode by deleting the entry FILE_SIZE_EXCEEDED(400_303,
HttpStatus.BAD_REQUEST, "파일 크기가 5MB를 초과합니다.") and any trailing comma/formatting
adjustments so the enum remains syntactically correct; ensure there are no
remaining references to ImageErrorCode.FILE_SIZE_EXCEEDED elsewhere (search for
FILE_SIZE_EXCEEDED) and run tests/compile to confirm the enum compiles after
removal.

In
`@src/main/java/com/pinHouse/server/platform/home/application/dto/HomeNoticeListResponse.java`:
- Around line 15-16: The Schema for the title field in HomeNoticeListResponse
should be marked nullable because HomeService.getDeadlineApproachingNotices()
sets title to null; update the `@Schema` annotation on the title field (in class
HomeNoticeListResponse) to include nullable = true so the API contract reflects
that the field can be null.

In
`@src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchCategoryType.java`:
- Around line 21-29: The static factory HomeSearchCategoryType.from currently
calls equalsIgnoreCase on the input and can NPE for null/blank inputs; add a
guard at the start of HomeSearchCategoryType.from(String value) that treats null
or blank (e.g., value == null || value.trim().isEmpty() or use
StringUtils.isBlank) as invalid and immediately throw new
CustomException(CommonErrorCode.BAD_PARAMETER); leave the existing loop and
error throw for non-matching values unchanged.

In
`@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java`:
- Around line 371-405: The current logic in HomeService checks
availableRentalTypes for the sentinel "해당 없음" and returns an empty
HomeNoticeListResponse even if other valid types exist; instead, remove the
contains("해당 없음") early-return and filter out that sentinel when building
targetSupplyTypes (use availableRentalTypes.stream().filter(...).filter(t ->
!t.equals("해당 없음"))...) so valid types are preserved; then keep the existing
check that returns an empty response only if the resulting targetSupplyTypes is
empty, updating log messages if needed to reference the filtered result.
- Around line 118-124: The response currently sets
HomeNoticeListResponse.builder().region to only county, losing higher-level
region info; update the code in HomeService (the HomeNoticeListResponse builder
call) to set region to a combined value such as (region + (county != null &&
!county.isEmpty() ? " " + county : "")) or equivalent null/empty-safe logic so
that when county exists the field contains "region county" and when county is
absent it contains just region; ensure you reference the existing variables used
in this method (region and county) and replace the single county assignment with
the combined, trimmed string.

In
`@src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepositoryImpl.java`:
- Around line 114-127: The searchByName method currently inserts the raw keyword
into Criteria.where("name").regex(...) causing regex injection; escape the
keyword before building the regex (e.g. use
java.util.regex.Pattern.quote(keyword) or a shared RegexUtils.escapeLiteral) and
then call Criteria.where("name").regex(escapedKeyword, "i"); also handle
null/empty keyword (return empty Slice or match-all as intended) and keep the
rest of the logic (query, pagination, mongoTemplate.find, SliceImpl) unchanged
so ComplexDocument results are safe from special-character/DoS injection.

In
`@src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java`:
- Around line 211-233: The method findRecommendedNoticesByDiagnosis in
NoticeDocumentRepositoryImpl currently removes the supplyType filter when
supplyTypes is null/empty and ends up scanning the whole collection; change it
to defensively return an empty Page (new PageImpl<>(Collections.emptyList(),
pageable, 0)) when supplyTypes is null or empty to avoid full collection scans
and unexpected results from other callers; keep the existing behavior when
supplyTypes is non-empty (apply criteria.and("supplyType").in(supplyTypes),
build the Query, run mongoTemplate.find and count) but short-circuit early for
null/empty supplyTypes.
- Around line 235-317: The code currently injects raw user input into
Criteria.regex in methods searchTargetGroups, searchRegions, searchHouseTypes,
searchNoticesByTargetGroup, searchNoticesByRegion and searchNoticesByHouseType
which allows regex metacharacters and ReDoS; fix by escaping the keyword (e.g.,
wrap with Pattern.quote(keyword) or a small helper escapeRegex(String)) before
passing to .regex(..., "i"), centralize the escaping in a private method (e.g.,
escapeForRegex) and call it from each of the six methods so all
Criteria.where(...).regex(...) calls use the escaped value; optionally consider
switching to a text-index based search if you need full-text semantics instead
of literal matching.

In
`@src/main/java/com/pinHouse/server/platform/image/application/service/ImageService.java`:
- Around line 68-75: In validateContentType, add a null check for the
contentType parameter and treat null or blank values as invalid (log a warning
and throw CustomException(ImageErrorCode.INVALID_FILE_TYPE)), and remove the
non-standard "image/jpg" entry from the allowed list so only standard MIME types
(e.g., "image/jpeg", "image/png", "image/gif") are checked; ensure you call
contentType.toLowerCase(Locale.ROOT) (or equivalent) only after verifying
non-null to avoid NullPointerException and update the log message to include the
original (possibly null) value for debugging.
🧹 Nitpick comments (14)
src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepository.java (1)

22-30: LGTM! 메서드 선언이 적절합니다.

무한 스크롤을 위한 Slice 반환 타입과 Pageable 파라미터 사용이 Spring Data 패턴에 부합합니다. Javadoc도 명확하게 작성되어 있습니다.

다만, 완전한 클래스 경로(fully qualified name) 대신 import문을 사용하면 가독성이 향상됩니다:

♻️ 선택적 개선안
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+
 public interface CustomComplexDocumentRepository {
     // ...
-    org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable);
+    Slice<ComplexDocument> searchByName(String keyword, Pageable pageable);
 }
src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepositoryImpl.java (1)

115-126: import문 사용으로 가독성 개선

완전한 클래스 경로 대신 import문을 사용하면 코드가 더 깔끔해집니다.

♻️ 개선안

파일 상단에 import 추가:

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

메서드 시그니처 및 반환문 수정:

 `@Override`
-public org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable) {
+public Slice<ComplexDocument> searchByName(String keyword, Pageable pageable) {
     // ...
-    return new org.springframework.data.domain.SliceImpl<>(content, pageable, hasNext);
+    return new SliceImpl<>(content, pageable, hasNext);
 }
src/main/java/com/pinHouse/server/platform/image/application/dto/PresignedUrlRequest.java (2)

17-20: contentType 정규식에서 'jpg' MIME 타입 검토 필요

image/jpg는 비표준 MIME 타입입니다. 표준 MIME 타입은 image/jpeg입니다. 대부분의 브라우저와 도구에서 image/jpeg만 사용하므로, jpg를 제거하는 것을 권장합니다.

♻️ 수정 제안
-        `@Pattern`(regexp = "^image/(jpeg|jpg|png|gif)$", message = "지원하지 않는 이미지 형식입니다")
+        `@Pattern`(regexp = "^image/(jpeg|png|gif)$", message = "지원하지 않는 이미지 형식입니다")

13-15: fileName에 대한 보안 검증 추가 권장

현재 fileName은 빈 값 검사만 수행합니다. Path traversal 공격(예: ../../../etc/passwd) 방지와 URL-safe 문자만 허용하기 위해 패턴 검증을 추가하는 것을 권장합니다.

♻️ 수정 제안
         `@NotBlank`(message = "파일명은 필수 입력값입니다")
+        `@Pattern`(regexp = "^[a-zA-Z0-9가-힣._-]+\\.(jpg|jpeg|png|gif)$", message = "유효하지 않은 파일명입니다")
         `@Schema`(description = "업로드할 파일명", example = "profile.jpg", required = true)
         String fileName,
src/main/java/com/pinHouse/server/core/config/S3Config.java (1)

20-24: 프로덕션 환경에서의 자격 증명 관리 방식 검토 필요

하드코딩된 AWS 자격 증명(access-key, secret-key)을 사용하는 것은 보안상 권장되지 않습니다. 프로덕션 환경에서는 IAM 역할, EC2 인스턴스 프로필, 또는 DefaultCredentialsProvider를 사용하는 것이 좋습니다.

♻️ 프로덕션 환경용 수정 제안
     `@Bean`
     public S3Client s3Client() {
-        AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
-
         return S3Client.builder()
                 .region(Region.of(region))
-                .credentialsProvider(StaticCredentialsProvider.create(credentials))
+                .credentialsProvider(DefaultCredentialsProvider.create())
                 .build();
     }

로컬/개발 환경용이라면 현재 구현도 괜찮으나, 환경별 프로파일 분리를 고려해 주세요.

src/main/java/com/pinHouse/server/platform/image/external/S3PresignedUrlGenerator.java (1)

74-80: Object Key URL 인코딩 누락

objectKey에 특수문자나 공백이 포함될 경우 URL이 깨질 수 있습니다. 현재 구현에서는 UUID와 확장자만 사용하므로 큰 문제는 없지만, 방어적으로 인코딩을 추가하는 것이 좋습니다.

♻️ 제안된 개선
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
 `@Override`
 public String getPublicUrl(String objectKey) {
+    String encodedKey = URLEncoder.encode(objectKey, StandardCharsets.UTF_8)
+            .replace("%2F", "/"); // 경로 구분자는 유지
     return String.format("https://%s.s3.%s.amazonaws.com/%s",
             bucketName,
             s3Client.serviceClientConfiguration().region().id(),
-            objectKey);
+            encodedKey);
 }
src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchSectionResponse.java (1)

19-23: content 리스트 방어적 복사 고려

record는 불변이지만 List는 외부에서 수정될 수 있습니다. of(...)에서 List.copyOf로 감싸면 응답 불변성이 강화됩니다. (null 허용이면 별도 처리 필요)

♻️ 제안 변경
 public static <T> HomeSearchSectionResponse<T> of(List<T> content, boolean hasNext) {
     return HomeSearchSectionResponse.<T>builder()
-            .content(content)
+            .content(List.copyOf(content))
             .hasNext(hasNext)
             .build();
 }
src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/dto/DiagnosisRecommendationGroup.java (1)

19-23: supplyTypes 리스트 방어적 복사 고려

응답 DTO의 리스트가 외부에서 변형될 수 있어 List.copyOf로 불변성 강화가 가능합니다.

♻️ 제안 변경
 public static DiagnosisRecommendationGroup of(String noticeType, List<String> supplyTypes) {
     return DiagnosisRecommendationGroup.builder()
             .noticeType(noticeType)
-            .supplyTypes(supplyTypes)
+            .supplyTypes(List.copyOf(supplyTypes))
             .build();
 }
src/main/java/com/pinHouse/server/platform/diagnostic/diagnosis/application/dto/DiagnosisResponseV2.java (1)

38-51: 중복 제거를 Set으로 단순화하면 성능/가독성이 좋아집니다

현재 List.contains로 중복 체크가 반복되어 후보 수가 커지면 O(n^2)로 커질 수 있습니다. LinkedHashSet으로 순서를 유지하면서 중복 제거를 단순화하는 쪽을 추천합니다.

♻️ 제안 변경
-import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@
-        Map<String, List<String>> grouped = new LinkedHashMap<>();
+        Map<String, LinkedHashSet<String>> grouped = new LinkedHashMap<>();
@@
-            grouped.computeIfAbsent(noticeType, k -> new ArrayList<>());
-            if (!grouped.get(noticeType).contains(supplyType)) {
-                grouped.get(noticeType).add(supplyType);
-            }
+            grouped.computeIfAbsent(noticeType, k -> new LinkedHashSet<>())
+                    .add(supplyType);
         }
 
         List<DiagnosisRecommendationGroup> groups = grouped.entrySet().stream()
-                .map(e -> DiagnosisRecommendationGroup.of(e.getKey(), e.getValue()))
+                .map(e -> DiagnosisRecommendationGroup.of(e.getKey(), List.copyOf(e.getValue())))
                 .toList();
src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryCustom.java (1)

67-108: 공급유형 목록이 비어 있을 때의 동작을 계약으로 명시해주세요.

supplyTypes가 null/empty일 때 전체 공고가 반환될 여지가 있으니(impl 기준), 빈 입력이면 빈 결과를 반환한다는 계약을 Javadoc에 명시하거나 구현에서 가드 처리하는 편이 안전합니다.

src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java (1)

320-330: 정렬이 없으면 Slice 페이지가 불안정할 수 있습니다.

findNoticeSlicepageable 정렬에만 의존합니다. 호출부에서 정렬이 비어 있으면 Mongo 자연 순서로 결과가 흔들릴 수 있으니 기본 정렬을 주는 편이 안전합니다.

♻️ 제안 변경
     private org.springframework.data.domain.Slice<NoticeDocument> findNoticeSlice(Criteria criteria, Pageable pageable) {
         Query query = new Query(criteria).with(pageable);
+        if (pageable.getSort().isUnsorted()) {
+            query.with(Sort.by(Sort.Order.asc("noticeId")));
+        }
         int limit = pageable.getPageSize();
         query.limit(limit + 1);
src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java (3)

196-243: 빈 검색어는 인기검색어에 기록하지 않도록 가드 권장.

공백/빈 문자열이 인기검색어에 누적되면 품질이 떨어질 수 있습니다. 기록 전에 간단한 가드를 두는 편이 안전합니다(카테고리 검색에도 동일 적용 권장).

♻️ 제안 변경
-        searchKeywordService.recordSearch(keyword, SearchKeywordScope.HOME);
+        if (keyword != null && !keyword.isBlank()) {
+            searchKeywordService.recordSearch(keyword, SearchKeywordScope.HOME);
+        }

292-295: limit 파라미터 상한/하한 클램핑을 권장합니다.

비정상적으로 큰 limit가 들어오면 부하가 증가할 수 있으니 적절한 상·하한을 두는 편이 안전합니다.

♻️ 제안 변경
     `@Override`
     public List<PopularKeywordResponse> getHomePopularKeywords(int limit) {
-        return searchKeywordService.getPopularKeywords(limit, SearchKeywordScope.HOME);
+        int capped = Math.max(1, Math.min(limit, 50));
+        return searchKeywordService.getPopularKeywords(capped, SearchKeywordScope.HOME);
     }

245-290: page 파라미터에 @Min(1) 검증 추가 권장

현재 코드에서 page 값이 1 미만이면 PageRequest.of(page - 1, ...)에서 IllegalArgumentException이 발생합니다. 전역 예외 핸들러에서 이를 HTTP 400으로 처리하고 있지만, 컨트롤러에서 @Min(value = 1) 검증 annotation을 추가하면 더 명확하고 안전합니다. 코드베이스에 이미 @Min 검증 패턴이 있으므로 일관성 있게 적용해주세요.

Comment on lines +52 to +55
// AWS SDK v2 for S3
implementation platform('software.amazon.awssdk:bom:2.21.0')
implementation 'software.amazon.awssdk:s3'
implementation 'software.amazon.awssdk:auth'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

latest AWS SDK for Java v2 BOM version 2024 2025

💡 Result:

Do you mean (A) the current/latest AWS SDK for Java v2 BOM version as of today (Jan 18, 2026), or (B) the latest BOM released in 2024 and the latest released in 2025?


🌐 Web query:

AWS SDK v2 2.21.0 security CVE vulnerabilities advisories

💡 Result:

Summary — AWS SDK for Java (v2) 2.21.0: known security advisories affecting that release

  • Maven listings for multiple 2.21.0 artifacts (s3, sts, sdk-core, apache-client, auth) show known CVEs in dependencies that affect that BOM/artifact set, including (as reported on Maven Repository): CVE-2024-47554, CVE-2023-41329, CVE-2025-48924 and others. See artifact pages for details per module. [1][2][3]

  • There is an open GitHub issue noting Netty dependency CVE-2023-44487 (HTTP/2 “rapid reset”) and discussion about upgrading Netty in aws-sdk-java-v2 releases; users should check whether your 2.21.0 runtime pulls an affected Netty version. [4]

Recommendation

  • Upgrade to a later aws-sdk-java-v2 release (2.33.x / 2.40.x+ as shown on Maven listings) or the latest patched version; verify updated dependency versions (Netty, etc.) in the newer release and retest. [1][2]
  • Run your dependency-vulnerability scanner (OWASP Dependency-Check / Snyk / GitHub Dependabot) against your built artifact to get the exact transitive CVE list for your environment.

Sources

  1. software.amazon.awssdk:s3 2.21.0 (Maven Repository) — vulnerability list and linked CVEs. [https://mvnrepository.com/artifact/software.amazon.awssdk/s3/2.21.0].
  2. software.amazon.awssdk:sts & sdk-core 2.21.0 (Maven Repository) — vulnerability listings. [https://mvnrepository.com/artifact/software.amazon.awssdk/sts/2.21.0] [https://mvnrepository.com/artifact/software.amazon.awssdk/sdk-core/2.21.0].
  3. software.amazon.awssdk:apache-client & auth 2.21.0 (Maven Repository) — dependency CVE listings. [https://mvnrepository.com/artifact/software.amazon.awssdk/apache-client/2.21.0] [https://mvnrepository.com/artifact/software.amazon.awssdk/auth/2.21.0].
  4. GitHub issue re: Netty dependency / CVE-2023-44487 on aws-sdk-java-v2. [https://github.com/Dependency of netty has an vulnerability CVE-2023-44487 'rapid reset' for HTTP/2 aws/aws-sdk-java-v2#4619].

If you want, I can:

  • produce the exact transitive CVE list for your project by scanning your pom/gradle coordinates, or
  • look up the fixed versions for each CVE and suggest minimal upgrades. Which would you prefer?

🌐 Web query:

AWS SDK v2 compatibility Spring Boot 3.4.3

💡 Result:

Short answer: Yes — AWS SDK for Java v2 is compatible with Spring Boot 3.4.3. Spring Cloud AWS 3.x is built on AWS SDK v2 and targets Spring Boot 3.x, so using SDK v2 (or Spring Cloud AWS 3.x which depends on it) is the recommended approach. [1][2]

Notes / pointers:

  • Spring Cloud AWS 3.x is explicitly built on AWS SDK v2. Use the 3.x awspring artifacts when integrating with Spring Boot 3.4.3. [2][3]
  • If you’re migrating from SDK v1, AWS provides a migration tool (OpenRewrite-based) to help convert code to SDK v2. [4]

Sources:

  1. Spring blog — Announcing Spring Cloud AWS 3.0.0 (built on AWS SDK v2). [2]
  2. AWSpring (spring-cloud-aws) GitHub compatibility table showing Spring Cloud AWS 3.x -> Spring Boot 3.x and use of SDK v2. [1]
  3. Spring Cloud AWS project page / compatibility notes. [3]
  4. AWS Developer Blog — Migration tool for AWS SDK for Java 2.x. [4]

AWS SDK BOM 버전 2.21.0은 여러 보안 취약점이 있어 즉시 업그레이드가 필요합니다.

버전 2.21.0은 다음을 포함하여 여러 알려진 CVE의 영향을 받습니다: CVE-2024-47554, CVE-2023-41329, CVE-2025-48924, CVE-2023-44487 (Netty HTTP/2). 버전 2.33.x 이상으로 업그레이드한 후 의존성 취약점 스캐너(OWASP Dependency-Check, Snyk, GitHub Dependabot)로 재검증하세요. Spring Boot 3.4.3과의 호환성은 문제없습니다.

🤖 Prompt for AI Agents
In `@build.gradle` around lines 52 - 55, Update the AWS SDK BOM dependency version
used for S3/auth from 'software.amazon.awssdk:bom:2.21.0' to at least 2.33.x to
remediate known CVEs; in the build.gradle block that declares implementation
platform('software.amazon.awssdk:bom:2.21.0') (alongside implementation
'software.amazon.awssdk:s3' and implementation 'software.amazon.awssdk:auth'),
bump the bom coordinate to a 2.33.x+ release and then re-run your dependency
vulnerability scanners (OWASP Dependency-Check, Snyk, Dependabot/GitHub) to
verify all transitive issues are resolved and compatible with Spring Boot 3.4.3.

// ========================
INVALID_FILE_TYPE(400_301, HttpStatus.BAD_REQUEST, "지원하지 않는 이미지 형식입니다. (jpg, jpeg, png, gif만 가능)"),
INVALID_FILE_EXTENSION(400_302, HttpStatus.BAD_REQUEST, "파일 확장자가 유효하지 않습니다."),
FILE_SIZE_EXCEEDED(400_303, HttpStatus.BAD_REQUEST, "파일 크기가 5MB를 초과합니다."),
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

🧩 Analysis chain

🏁 Script executed:

rg -n "FILE_SIZE_EXCEEDED" --type java

Repository: PinHouse/PinHouse_BE

Length of output: 219


🏁 Script executed:

rg -n "400_303" --type java

Repository: PinHouse/PinHouse_BE

Length of output: 219


🏁 Script executed:

rg -n "FILE_SIZE|파일 크기" --type java -i

Repository: PinHouse/PinHouse_BE

Length of output: 379


FILE_SIZE_EXCEEDED 에러 코드를 제거하세요

FILE_SIZE_EXCEEDED는 정의되었지만 코드베이스에서 사용되지 않는 dead code입니다. 스웨거 문서에도 "파일 크기 전달은 필요 없으며"라고 명시되어 있어 파일 크기 유효성 검사가 제거되었음을 확인할 수 있습니다. 이 미사용 에러 코드는 제거되어야 합니다.

🤖 Prompt for AI Agents
In `@src/main/java/com/pinHouse/server/core/exception/code/ImageErrorCode.java` at
line 20, Remove the unused enum constant FILE_SIZE_EXCEEDED from ImageErrorCode
by deleting the entry FILE_SIZE_EXCEEDED(400_303, HttpStatus.BAD_REQUEST, "파일
크기가 5MB를 초과합니다.") and any trailing comma/formatting adjustments so the enum
remains syntactically correct; ensure there are no remaining references to
ImageErrorCode.FILE_SIZE_EXCEEDED elsewhere (search for FILE_SIZE_EXCEEDED) and
run tests/compile to confirm the enum compiles after removal.

Comment on lines +15 to +16
@Schema(description = "목록 설명/출처", example = "진단 기반 추천")
String title,
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

🧩 Analysis chain

🏁 Script executed:

# First, find and read the HomeNoticeListResponse.java file
fd -t f "HomeNoticeListResponse.java" | head -5

Repository: PinHouse/PinHouse_BE

Length of output: 155


🏁 Script executed:

# Search for the file more broadly
find . -name "HomeNoticeListResponse.java" -type f

Repository: PinHouse/PinHouse_BE

Length of output: 157


🏁 Script executed:

# List files in the home/application/dto directory
fd -t f . "src/main/java/com/pinHouse/server/platform/home/application/dto"

Repository: PinHouse/PinHouse_BE

Length of output: 803


🏁 Script executed:

# Read the HomeNoticeListResponse.java file
cat -n src/main/java/com/pinHouse/server/platform/home/application/dto/HomeNoticeListResponse.java

Repository: PinHouse/PinHouse_BE

Length of output: 1035


🏁 Script executed:

# Search for where HomeNoticeListResponse is instantiated or built
rg "HomeNoticeListResponse" --type java -A 3 -B 1

Repository: PinHouse/PinHouse_BE

Length of output: 13096


🏁 Script executed:

# Look at the HomeService implementation more carefully
rg -A 20 "getDeadlineApproachingNotices" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | head -50

Repository: PinHouse/PinHouse_BE

Length of output: 964


🏁 Script executed:

# Also check the getRecommendedNoticesByDiagnosis method
rg -A 30 "getRecommendedNoticesByDiagnosis" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | grep -A 10 "HomeNoticeListResponse.builder"

Repository: PinHouse/PinHouse_BE

Length of output: 359


🏁 Script executed:

# Get the exact line where title is set to null in HomeService
rg -n "\.title\(null\)" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 92


🏁 Script executed:

# Also check all title assignments
rg -n "\.title\(" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 205


🏁 Script executed:

# Get the full context around line 120 where title is set to null
sed -n '110,130p' src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 800


🏁 Script executed:

# Check if there are any other annotations or constraints on the title field in the DTO
cat -n src/main/java/com/pinHouse/server/platform/home/application/dto/HomeNoticeListResponse.java | sed -n '14,18p'

Repository: PinHouse/PinHouse_BE

Length of output: 225


title 필드를 nullable로 명시 필요

HomeService.getDeadlineApproachingNotices()에서 title을 명시적으로 null로 설정하고 있으나, 현재 @Schema 어노테이션은 nullable 여부를 표시하지 않습니다. API 계약을 명확히 하기 위해 nullable = true를 추가하세요.

수정 사항
-        `@Schema`(description = "목록 설명/출처", example = "진단 기반 추천")
+        `@Schema`(description = "목록 설명/출처", example = "진단 기반 추천", nullable = true)
         String title,
📝 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
@Schema(description = "목록 설명/출처", example = "진단 기반 추천")
String title,
`@Schema`(description = "목록 설명/출처", example = "진단 기반 추천", nullable = true)
String title,
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/home/application/dto/HomeNoticeListResponse.java`
around lines 15 - 16, The Schema for the title field in HomeNoticeListResponse
should be marked nullable because HomeService.getDeadlineApproachingNotices()
sets title to null; update the `@Schema` annotation on the title field (in class
HomeNoticeListResponse) to include nullable = true so the API contract reflects
that the field can be null.

Comment on lines +21 to +29
@JsonCreator
public static HomeSearchCategoryType from(String value) {
for (HomeSearchCategoryType type : values()) {
if (type.value.equalsIgnoreCase(value)) {
return type;
}
}
throw new CustomException(CommonErrorCode.BAD_PARAMETER);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

null/blank 입력 시 NPE로 500 발생 가능.

equalsIgnoreCase는 null에 취약합니다. 잘못된 입력을 BAD_PARAMETER로 처리하도록 가드가 필요합니다.

🛠️ 제안 수정안
 `@JsonCreator`
 public static HomeSearchCategoryType from(String value) {
+    if (value == null || value.isBlank()) {
+        throw new CustomException(CommonErrorCode.BAD_PARAMETER);
+    }
     for (HomeSearchCategoryType type : values()) {
         if (type.value.equalsIgnoreCase(value)) {
             return type;
         }
     }
     throw new CustomException(CommonErrorCode.BAD_PARAMETER);
 }
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/home/application/dto/HomeSearchCategoryType.java`
around lines 21 - 29, The static factory HomeSearchCategoryType.from currently
calls equalsIgnoreCase on the input and can NPE for null/blank inputs; add a
guard at the start of HomeSearchCategoryType.from(String value) that treats null
or blank (e.g., value == null || value.trim().isEmpty() or use
StringUtils.isBlank) as invalid and immediately throw new
CustomException(CommonErrorCode.BAD_PARAMETER); leave the existing loop and
error throw for non-matching values unchanged.

Comment on lines 118 to +124
return HomeNoticeListResponse.builder()
.region(county)
.content(content)
.hasNext(page.hasNext())
.totalElements(page.getTotalElements())
.build();
.region(county)
.title(null)
.content(content)
.hasNext(page.hasNext())
.totalElements(page.getTotalElements())
.build();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

응답 region에 county만 넣으면 지역 정보가 누락됩니다.

현재 region 값이 county로만 채워져 광역 단위 정보가 사라질 수 있습니다. region + county 조합(또는 county가 없을 때 region)을 사용하도록 조정하는 편이 안전합니다.

🐛 제안 변경
-        return HomeNoticeListResponse.builder()
-            .region(county)
+        String regionLabel = buildAddress(region.getFullName(), county);
+        return HomeNoticeListResponse.builder()
+            .region(regionLabel)
             .title(null)
             .content(content)
             .hasNext(page.hasNext())
             .totalElements(page.getTotalElements())
             .build();
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java`
around lines 118 - 124, The response currently sets
HomeNoticeListResponse.builder().region to only county, losing higher-level
region info; update the code in HomeService (the HomeNoticeListResponse builder
call) to set region to a combined value such as (region + (county != null &&
!county.isEmpty() ? " " + county : "")) or equivalent null/empty-safe logic so
that when county exists the field contains "region county" and when county is
absent it contains just region; ensure you reference the existing variables used
in this method (region and county) and replace the single county assignment with
the combined, trimmed string.

Comment on lines +371 to +405
// 3. 추천 임대주택 유형 추출
List<String> availableRentalTypes = diagnosis.availableRentalTypes();

// 4. 자격 없는 경우 빈 응답 반환
if (availableRentalTypes == null ||
availableRentalTypes.isEmpty() ||
availableRentalTypes.contains("해당 없음")) {
log.info("추천 가능한 임대주택이 없습니다 - userId={}", userId);
return HomeNoticeListResponse.builder()
.region(null)
.title("진단 기반 추천")
.content(List.of())
.hasNext(false)
.totalElements(0L)
.build();
}

// 5. 진단 결과 → 공고 supplyType 매핑
List<String> targetSupplyTypes = availableRentalTypes.stream()
.filter(type -> type != null && !type.isBlank())
.distinct()
.toList();

// 진단 결과에 매핑될 공고 유형이 없는 경우 빈 응답 반환
if (targetSupplyTypes.isEmpty()) {
log.info("진단 결과에 매핑 가능한 주택 유형이 없습니다 - userId={}", userId);
return HomeNoticeListResponse.builder()
.region(null)
.title("진단 기반 추천")
.content(List.of())
.hasNext(false)
.totalElements(0L)
.build();
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

'해당 없음'이 섞여 있어도 다른 유형을 버리지 않도록 필터링 권장.

현재는 availableRentalTypes"해당 없음"이 포함되면 다른 유효 값이 있어도 즉시 빈 결과를 반환합니다. 유효 값은 유지하고 sentinel만 제거하는 편이 안전합니다.

🐛 제안 변경
-        List<String> availableRentalTypes = diagnosis.availableRentalTypes();
-
-        // 4. 자격 없는 경우 빈 응답 반환
-        if (availableRentalTypes == null ||
-            availableRentalTypes.isEmpty() ||
-            availableRentalTypes.contains("해당 없음")) {
-            log.info("추천 가능한 임대주택이 없습니다 - userId={}", userId);
-            return HomeNoticeListResponse.builder()
-                .region(null)
-                .title("진단 기반 추천")
-                .content(List.of())
-                .hasNext(false)
-                .totalElements(0L)
-                .build();
-        }
-
-        // 5. 진단 결과 → 공고 supplyType 매핑
-        List<String> targetSupplyTypes = availableRentalTypes.stream()
-            .filter(type -> type != null && !type.isBlank())
-            .distinct()
-            .toList();
+        List<String> availableRentalTypes = diagnosis.availableRentalTypes();
+
+        // 4. 진단 결과 → 공고 supplyType 매핑 (sentinel 제거)
+        List<String> targetSupplyTypes = availableRentalTypes == null ? List.of()
+            : availableRentalTypes.stream()
+                .filter(type -> type != null && !type.isBlank() && !"해당 없음".equals(type))
+                .distinct()
+                .toList();
 
         // 진단 결과에 매핑될 공고 유형이 없는 경우 빈 응답 반환
         if (targetSupplyTypes.isEmpty()) {
             log.info("진단 결과에 매핑 가능한 주택 유형이 없습니다 - userId={}", userId);
             return HomeNoticeListResponse.builder()
                 .region(null)
                 .title("진단 기반 추천")
                 .content(List.of())
                 .hasNext(false)
                 .totalElements(0L)
                 .build();
         }
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java`
around lines 371 - 405, The current logic in HomeService checks
availableRentalTypes for the sentinel "해당 없음" and returns an empty
HomeNoticeListResponse even if other valid types exist; instead, remove the
contains("해당 없음") early-return and filter out that sentinel when building
targetSupplyTypes (use availableRentalTypes.stream().filter(...).filter(t ->
!t.equals("해당 없음"))...) so valid types are preserved; then keep the existing
check that returns an empty response only if the resulting targetSupplyTypes is
empty, updating log messages if needed to reference the filtered result.

Comment on lines +114 to +127
@Override
public org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable) {
Criteria criteria = Criteria.where("name").regex(keyword, "i");
Query query = new Query(criteria).with(pageable);

int limit = pageable.getPageSize();
query.limit(limit + 1);

List<ComplexDocument> complexes = mongoTemplate.find(query, ComplexDocument.class);
boolean hasNext = complexes.size() > limit;
List<ComplexDocument> content = hasNext ? complexes.subList(0, limit) : complexes;

return new org.springframework.data.domain.SliceImpl<>(content, pageable, hasNext);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Regex 인젝션 취약점: 키워드 이스케이프 필요

keyword가 정규식에 직접 사용되므로, 특수 문자(예: .*, (), [] 등)가 포함된 경우 예기치 않은 동작이나 PatternSyntaxException이 발생할 수 있습니다. 악의적인 입력 시 ReDoS 공격에도 취약할 수 있습니다.

🔒 권장 수정안: 정규식 특수 문자 이스케이프
 `@Override`
 public org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable) {
-    Criteria criteria = Criteria.where("name").regex(keyword, "i");
+    String escapedKeyword = java.util.regex.Pattern.quote(keyword);
+    Criteria criteria = Criteria.where("name").regex(escapedKeyword, "i");
     Query query = new Query(criteria).with(pageable);
     // ...
 }
📝 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
@Override
public org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable) {
Criteria criteria = Criteria.where("name").regex(keyword, "i");
Query query = new Query(criteria).with(pageable);
int limit = pageable.getPageSize();
query.limit(limit + 1);
List<ComplexDocument> complexes = mongoTemplate.find(query, ComplexDocument.class);
boolean hasNext = complexes.size() > limit;
List<ComplexDocument> content = hasNext ? complexes.subList(0, limit) : complexes;
return new org.springframework.data.domain.SliceImpl<>(content, pageable, hasNext);
}
`@Override`
public org.springframework.data.domain.Slice<ComplexDocument> searchByName(String keyword, org.springframework.data.domain.Pageable pageable) {
String escapedKeyword = java.util.regex.Pattern.quote(keyword);
Criteria criteria = Criteria.where("name").regex(escapedKeyword, "i");
Query query = new Query(criteria).with(pageable);
int limit = pageable.getPageSize();
query.limit(limit + 1);
List<ComplexDocument> complexes = mongoTemplate.find(query, ComplexDocument.class);
boolean hasNext = complexes.size() > limit;
List<ComplexDocument> content = hasNext ? complexes.subList(0, limit) : complexes;
return new org.springframework.data.domain.SliceImpl<>(content, pageable, hasNext);
}
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/housing/complex/domain/repository/CustomComplexDocumentRepositoryImpl.java`
around lines 114 - 127, The searchByName method currently inserts the raw
keyword into Criteria.where("name").regex(...) causing regex injection; escape
the keyword before building the regex (e.g. use
java.util.regex.Pattern.quote(keyword) or a shared RegexUtils.escapeLiteral) and
then call Criteria.where("name").regex(escapedKeyword, "i"); also handle
null/empty keyword (return empty Slice or match-all as intended) and keep the
rest of the logic (query, pagination, mongoTemplate.find, SliceImpl) unchanged
so ComplexDocument results are safe from special-character/DoS injection.

Comment on lines +211 to +233
@Override
public Page<NoticeDocument> findRecommendedNoticesByDiagnosis(
java.util.List<String> supplyTypes,
Pageable pageable
) {
Criteria criteria = new Criteria();

/// supplyType 필터링 (진단 결과에서 매핑된 주택 유형)
if (supplyTypes != null && !supplyTypes.isEmpty()) {
criteria.and("supplyType").in(supplyTypes);
}

/// 모집 상태 필터링 없음 (모든 공고 포함)
/// 정렬은 Pageable의 Sort로 처리 (마감임박순 권장)

Query query = new Query(criteria).with(pageable);

/// 실행 및 Page 응답 구성
List<NoticeDocument> notices = mongoTemplate.find(query, NoticeDocument.class);
long count = mongoTemplate.count(Query.of(query).limit(-1).skip(-1), NoticeDocument.class);

return new PageImpl<>(notices, pageable, count);
}
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

🧩 Analysis chain

🏁 Script executed:

# Search for the method definition and its usage
rg -n "findRecommendedNoticesByDiagnosis" --type java

Repository: PinHouse/PinHouse_BE

Length of output: 596


🏁 Script executed:

# Look for test cases related to this method
fd -e java | xargs grep -l "findRecommendedNoticesByDiagnosis" | head -20

Repository: PinHouse/PinHouse_BE

Length of output: 370


🏁 Script executed:

# Examine similar filter methods in the repository to understand pattern
rg "public Page.*find.*Criteria" src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/ -A 10 -B 2 --type java

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

# Check the full NoticeDocumentRepositoryImpl file structure
wc -l src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java

Repository: PinHouse/PinHouse_BE

Length of output: 177


🏁 Script executed:

# Get context around the method in question
sed -n '200,240p' src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java

Repository: PinHouse/PinHouse_BE

Length of output: 1636


🏁 Script executed:

# Check for any documentation or comments about this method's purpose
sed -n '200,250p' src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java | grep -E "(@|//|/\*)"

Repository: PinHouse/PinHouse_BE

Length of output: 276


🏁 Script executed:

# Check the caller in HomeService to understand context
sed -n '410,425p' src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 723


🏁 Script executed:

# Check if there are test cases for this method
fd -e java test | xargs grep -l "findRecommendedNoticesByDiagnosis" 2>/dev/null | head -5

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

# Check the interface definition to understand the contract
cat src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryCustom.java

Repository: PinHouse/PinHouse_BE

Length of output: 3431


🏁 Script executed:

# Check other similar find methods in the same implementation file to see if there are guards
sed -n '1,200p' src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java | grep -A 15 "public Page.*find"

Repository: PinHouse/PinHouse_BE

Length of output: 1331


🏁 Script executed:

# Check where targetSupplyTypes is set in HomeService
sed -n '400,420p' src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 815


🏁 Script executed:

# Look for more context around the diagnosis feature and targetSupplyTypes assignment
sed -n '380,430p' src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java

Repository: PinHouse/PinHouse_BE

Length of output: 1834


🏁 Script executed:

# Check if there are any guards before calling findRecommendedNoticesByDiagnosis
grep -B 10 "findRecommendedNoticesByDiagnosis" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | head -20

Repository: PinHouse/PinHouse_BE

Length of output: 522


🏁 Script executed:

# Check all callers of findRecommendedNoticesByDiagnosis to ensure there's only one
grep -r "findRecommendedNoticesByDiagnosis" src/ --include="*.java" | grep -v "public.*findRecommendedNoticesByDiagnosis" | grep -v "@Override" | grep -v "interface"

Repository: PinHouse/PinHouse_BE

Length of output: 408


🏁 Script executed:

# Double-check the exact implementation to see the behavior when supplyTypes is empty
sed -n '211,235p' src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java

Repository: PinHouse/PinHouse_BE

Length of output: 887


진단 기반 추천 조회 시 공급유형이 비어 있으면 전체 공고를 스캔합니다.

supplyTypes가 null/empty일 때 필터 조건이 제거되어 전체 컬렉션을 스캔하게 됩니다. 현재 HomeService에서 사전에 검증하고 있지만, 이 메서드는 공개 인터페이스(NoticeDocumentRepositoryCustom)의 일부이므로 다른 호출처에서 사용될 수 있습니다. 저방어적 프로그래밍을 위해 빈 목록 반환으로 처리하는 것이 안전합니다.

제안 변경
 public Page<NoticeDocument> findRecommendedNoticesByDiagnosis(
         java.util.List<String> supplyTypes,
         Pageable pageable
 ) {
+    if (supplyTypes == null || supplyTypes.isEmpty()) {
+        return new PageImpl<>(List.of(), pageable, 0);
+    }
     Criteria criteria = new Criteria();
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java`
around lines 211 - 233, The method findRecommendedNoticesByDiagnosis in
NoticeDocumentRepositoryImpl currently removes the supplyType filter when
supplyTypes is null/empty and ends up scanning the whole collection; change it
to defensively return an empty Page (new PageImpl<>(Collections.emptyList(),
pageable, 0)) when supplyTypes is null or empty to avoid full collection scans
and unexpected results from other callers; keep the existing behavior when
supplyTypes is non-empty (apply criteria.and("supplyType").in(supplyTypes),
build the Query, run mongoTemplate.find and count) but short-circuit early for
null/empty supplyTypes.

Comment on lines +235 to +317
@Override
public org.springframework.data.domain.Slice<String> searchTargetGroups(String keyword, Pageable pageable) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("targetGroup").regex(keyword, "i")),
Aggregation.unwind("targetGroup"),
Aggregation.match(Criteria.where("targetGroup").regex(keyword, "i")),
Aggregation.group("targetGroup"),
Aggregation.sort(Sort.by(Sort.Order.asc("_id"))),
Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
Aggregation.limit(pageable.getPageSize() + 1)
);

List<String> results = mongoTemplate.aggregate(aggregation, "notices", StringAggregationResult.class)
.getMappedResults()
.stream()
.map(StringAggregationResult::getId)
.toList();

return toSlice(results, pageable);
}

@Override
public org.springframework.data.domain.Slice<String> searchRegions(String keyword, Pageable pageable) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(new Criteria().orOperator(
Criteria.where("city").regex(keyword, "i"),
Criteria.where("county").regex(keyword, "i")
)),
Aggregation.project("city", "county")
.andExpression("concat(city, ' ', county)").as("region"),
Aggregation.group("region"),
Aggregation.sort(Sort.by(Sort.Order.asc("_id"))),
Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
Aggregation.limit(pageable.getPageSize() + 1)
);

List<String> results = mongoTemplate.aggregate(aggregation, "notices", StringAggregationResult.class)
.getMappedResults()
.stream()
.map(StringAggregationResult::getId)
.toList();

return toSlice(results, pageable);
}

@Override
public org.springframework.data.domain.Slice<String> searchHouseTypes(String keyword, Pageable pageable) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("houseType").regex(keyword, "i")),
Aggregation.group("houseType"),
Aggregation.sort(Sort.by(Sort.Order.asc("_id"))),
Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
Aggregation.limit(pageable.getPageSize() + 1)
);

List<String> results = mongoTemplate.aggregate(aggregation, "notices", StringAggregationResult.class)
.getMappedResults()
.stream()
.map(StringAggregationResult::getId)
.toList();

return toSlice(results, pageable);
}

@Override
public org.springframework.data.domain.Slice<NoticeDocument> searchNoticesByTargetGroup(String keyword, Pageable pageable) {
Criteria criteria = Criteria.where("targetGroup").regex(keyword, "i");
return findNoticeSlice(criteria, pageable);
}

@Override
public org.springframework.data.domain.Slice<NoticeDocument> searchNoticesByRegion(String keyword, Pageable pageable) {
Criteria criteria = new Criteria().orOperator(
Criteria.where("city").regex(keyword, "i"),
Criteria.where("county").regex(keyword, "i")
);
return findNoticeSlice(criteria, pageable);
}

@Override
public org.springframework.data.domain.Slice<NoticeDocument> searchNoticesByHouseType(String keyword, Pageable pageable) {
Criteria criteria = Criteria.where("houseType").regex(keyword, "i");
return findNoticeSlice(criteria, pageable);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "NoticeDocumentRepositoryImpl.java"

Repository: PinHouse/PinHouse_BE

Length of output: 175


🏁 Script executed:

wc -l ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java

Repository: PinHouse/PinHouse_BE

Length of output: 179


🏁 Script executed:

cat -n ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java | sed -n '230,320p'

Repository: PinHouse/PinHouse_BE

Length of output: 4898


🏁 Script executed:

cat -n ./src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java | head -50

Repository: PinHouse/PinHouse_BE

Length of output: 2399


🏁 Script executed:

rg -t java "searchTargetGroups|searchRegions|searchHouseTypes|searchNoticesByTargetGroup|searchNoticesByRegion|searchNoticesByHouseType" --max-count 20 -A 3

Repository: PinHouse/PinHouse_BE

Length of output: 11620


🏁 Script executed:

cat -n ./src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | grep -B 15 "searchNoticesByTargetGroup" | head -30

Repository: PinHouse/PinHouse_BE

Length of output: 1602


🏁 Script executed:

rg -t java "public.*homeSearchOverview|public.*homeSearchCategory" -A 10 src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | head -40

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

rg -t java "keyword" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java -B 5 | head -80

Repository: PinHouse/PinHouse_BE

Length of output: 3314


🏁 Script executed:

rg -t java "class HomeService|public.*searchHomeOverview|public.*searchHomeByCategory" -A 2 src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java | head -20

Repository: PinHouse/PinHouse_BE

Length of output: 484


🏁 Script executed:

rg -t java "@RequestParam|@PathVariable" src/main/java/com/pinHouse/server/platform/home -B 2 -A 8 | grep -A 10 "keyword"

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

find ./src/main/java/com/pinHouse/server/platform/home -name "*Controller.java" -type f

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

rg -t java "searchHomeOverview|searchHomeByCategory" --max-count 20

Repository: PinHouse/PinHouse_BE

Length of output: 1013


🏁 Script executed:

cat -n ./src/main/java/com/pinHouse/server/platform/home/presentation/HomeApi.java

Repository: PinHouse/PinHouse_BE

Length of output: 6541


🏁 Script executed:

rg -t java "Pattern.quote|safeRegex|escapeRegex|sanitize" --max-count 20

Repository: PinHouse/PinHouse_BE

Length of output: 46


🏁 Script executed:

rg -t java "keyword" src/main/java/com/pinHouse/server/platform/home/application/service/HomeService.java -B 2 -A 2 | head -60

Repository: PinHouse/PinHouse_BE

Length of output: 2784


🏁 Script executed:

web_search
MongoDB Spring Data regex method and Pattern.quote usage

Repository: PinHouse/PinHouse_BE

Length of output: 160


사용자 입력을 정규식에 그대로 넣는 것은 보안 위험입니다.

keyword 파라미터가 HTTP 요청으로부터 직접 전달되어 검증 없이 .regex() 메서드에 사용됩니다. 정규식 메타 문자가 포함될 경우 과도한 매칭 또는 ReDoS 공격으로 이어질 수 있습니다. 6개 메서드(searchTargetGroups, searchRegions, searchHouseTypes, searchNoticesByTargetGroup, searchNoticesByRegion, searchNoticesByHouseType)에서 동일한 패턴이 반복되고 있으므로 Pattern.quote()를 이용하여 입력을 이스케이프하거나 텍스트 인덱스 기반 검색으로 전환을 권장합니다.

🔒 제안 변경
+import java.util.regex.Pattern;
...
-        Aggregation.match(Criteria.where("targetGroup").regex(keyword, "i")),
+        Aggregation.match(Criteria.where("targetGroup").regex(safeRegex(keyword))),
...
-        Aggregation.match(new Criteria().orOperator(
-                Criteria.where("city").regex(keyword, "i"),
-                Criteria.where("county").regex(keyword, "i")
-        )),
+        Aggregation.match(new Criteria().orOperator(
+                Criteria.where("city").regex(safeRegex(keyword)),
+                Criteria.where("county").regex(safeRegex(keyword))
+        )),
...
-        Aggregation.match(Criteria.where("houseType").regex(keyword, "i")),
+        Aggregation.match(Criteria.where("houseType").regex(safeRegex(keyword))),
...
-        Criteria criteria = Criteria.where("targetGroup").regex(keyword, "i");
+        Criteria criteria = Criteria.where("targetGroup").regex(safeRegex(keyword));
...
-        Criteria criteria = new Criteria().orOperator(
-                Criteria.where("city").regex(keyword, "i"),
-                Criteria.where("county").regex(keyword, "i")
-        );
+        Criteria criteria = new Criteria().orOperator(
+                Criteria.where("city").regex(safeRegex(keyword)),
+                Criteria.where("county").regex(safeRegex(keyword))
+        );
...
-        Criteria criteria = Criteria.where("houseType").regex(keyword, "i");
+        Criteria criteria = Criteria.where("houseType").regex(safeRegex(keyword));
...
+    private String safeRegex(String keyword) {
+        return Pattern.quote(keyword);
+    }
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/housing/notice/domain/repository/NoticeDocumentRepositoryImpl.java`
around lines 235 - 317, The code currently injects raw user input into
Criteria.regex in methods searchTargetGroups, searchRegions, searchHouseTypes,
searchNoticesByTargetGroup, searchNoticesByRegion and searchNoticesByHouseType
which allows regex metacharacters and ReDoS; fix by escaping the keyword (e.g.,
wrap with Pattern.quote(keyword) or a small helper escapeRegex(String)) before
passing to .regex(..., "i"), centralize the escaping in a private method (e.g.,
escapeForRegex) and call it from each of the six methods so all
Criteria.where(...).regex(...) calls use the escaped value; optionally consider
switching to a text-index based search if you need full-text semantics instead
of literal matching.

Comment on lines +68 to +75
private void validateContentType(String contentType) {
List<String> allowedContentTypes = List.of("image/jpeg", "image/jpg", "image/png", "image/gif");

if (!allowedContentTypes.contains(contentType.toLowerCase())) {
log.warn("지원하지 않는 Content-Type: {}", contentType);
throw new CustomException(ImageErrorCode.INVALID_FILE_TYPE);
}
}
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

Null 체크 누락 및 잘못된 MIME 타입

두 가지 이슈가 있습니다:

  1. contentType이 null일 경우 NullPointerException이 발생합니다.
  2. "image/jpg"는 유효한 MIME 타입이 아닙니다. 표준 MIME 타입은 "image/jpeg"입니다.
🐛 제안된 수정
 private void validateContentType(String contentType) {
-    List<String> allowedContentTypes = List.of("image/jpeg", "image/jpg", "image/png", "image/gif");
+    List<String> allowedContentTypes = List.of("image/jpeg", "image/png", "image/gif");

-    if (!allowedContentTypes.contains(contentType.toLowerCase())) {
+    if (contentType == null || !allowedContentTypes.contains(contentType.toLowerCase())) {
         log.warn("지원하지 않는 Content-Type: {}", contentType);
         throw new CustomException(ImageErrorCode.INVALID_FILE_TYPE);
     }
 }
🤖 Prompt for AI Agents
In
`@src/main/java/com/pinHouse/server/platform/image/application/service/ImageService.java`
around lines 68 - 75, In validateContentType, add a null check for the
contentType parameter and treat null or blank values as invalid (log a warning
and throw CustomException(ImageErrorCode.INVALID_FILE_TYPE)), and remove the
non-standard "image/jpg" entry from the allowed list so only standard MIME types
(e.g., "image/jpeg", "image/png", "image/gif") are checked; ensure you call
contentType.toLowerCase(Locale.ROOT) (or equivalent) only after verifying
non-null to avoid NullPointerException and update the log message to include the
original (possibly null) value for debugging.

@doup2001 doup2001 merged commit e5d098f into develop Jan 18, 2026
3 of 4 checks passed
@doup2001 doup2001 deleted the feat/이미지-관련-기능-구현 branch January 18, 2026 04:19
@doup2001 doup2001 self-assigned this Jan 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants