Skip to content

[Refactor] 매물 페이지 API 연동#87

Merged
MeongW merged 38 commits intodevelopfrom
refactor/homeCreate
Aug 17, 2025
Merged

[Refactor] 매물 페이지 API 연동#87
MeongW merged 38 commits intodevelopfrom
refactor/homeCreate

Conversation

@seonju21
Copy link
Contributor

@seonju21 seonju21 commented Aug 14, 2025

🚀 관련 이슈

🔑 주요 변경사항

  • 매물 등록 가능
  • 매물 리스트 조회 가능 (필터기능은 아직 덜되긴했습니다...
  • 매물 상세페이지 조회 가능

✔️ 체크 리스트

  • Merge 하려는 브랜치가 올바른가? (main branch에 실수로 PR 생성 금지)
  • Merge 하려는 PR 및 Commit들을 로컬에서 실행했을 때 에러가 발생하지 않았는가?
  • 라벨을 등록했는가?
  • 리뷰어를 지정했는가?

📢 To Reviewers

📸 스크린샷 or 실행영상

↗️ 개선 사항

Summary by CodeRabbit

  • 신규 기능

    • 매물 등록/수정 시 이미지 멀티파트 업로드(S3 연동) 지원
    • 내 매물 조회(/my), 매물 찜 토글 및 찜 목록 조회 추가
    • 시설 카테고리/항목 조회 및 고급 검색(다양한 필터 + 페이징) 추가
    • 매물 상태 변경(PATCH) 엔드포인트 추가
  • 변경 사항

    • 기본 페이지 크기 21로 조정
    • 생성 API가 생성된 ID(Integer)를 반환하도록 변경
    • 응답/요청 DTO 명칭·스키마 확장(이미지·시설·관리비 등)
    • 폼 데이터 → LocalDate 자동 변환 지원

leeedongjaee and others added 30 commits August 11, 2025 11:39
- HomeCreateRequestDto, HomeUpdateRequestDto 신규 생성
- HomeCreateDTO에서 imageUrls 필드 제거 (파일 업로드만 지원)
- HomeImageVO 필드명 수정 (ImageUrl → imageUrl)
- Swagger 어노테이션 추가 및 예제값 설정
- selectHomeFacilitiesByHomeDetailId 중복 제거
- selectHomeMaintenanceFeesByHomeId 중복 제거
- selectFacilityCategories 중복 제거
- selectFacilityItemsByCategoryId 중복 제거
- incrementViewCount 중복 제거
- HomeController 인터페이스 정의
- HomeControllerImpl 구현 클래스 생성
- API 정의와 비즈니스 로직 분리
- 테스트 가능성 및 유지보수성 향상
- 불필요한 디버깅 로그 제거
- 이미지 업로드 로직 단순화
- S3 경로 처리 개선 (home/{homeId}/{fileName} 형식)
- 에러 처리 및 로깅 메시지 최적화
- updateHome 메서드에 이미지 파라미터 추가
- 매물 수정 시 이미지 업로드 기능 지원
- 메서드 호출 공백 일관성 개선
- 코드 포맷팅 통일
- LocalDateTime.toLocalDate() 호출 제거
- buildDate가 이미 LocalDate 타입으로 처리되도록 수정
- HomeCreateRequestDto/HomeUpdateRequestDto import 경로 수정
- buildDate 타입을 LocalDateTime에서 LocalDate로 변경
- 필드명 매핑 수정 (bathroomCount -> bathroomCnt, isParkingAvailable -> isParking)
- MaintenanceFeeDTO를 MaintenanceFeeItem으로 변환하는 로직 추가
- Integer를 Long으로 변환하는 로직 추가
- multipart/form-data에서 LocalDate 타입 변환 지원
- ISO_LOCAL_DATE 포맷 (yyyy-MM-dd) 처리
- 빈 문자열 처리 지원
@seonju21 seonju21 self-assigned this Aug 14, 2025
@seonju21 seonju21 added the ♻️ refactor 리팩토링 작업 label Aug 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

Home 도메인 전면 리팩터링: 컨트롤러 인터페이스/구현 분리, DTO/VO 재설계, ID 타입 Long→Integer 변경, 멀티파트(S3) 이미지 처리 추가, Mapper(SQL)·Service 로직 재구성, 페이징/검색/좋아요/시설/관리비/상태 변경 엔드포인트 추가 및 LocalDate 바인딩 등록.

Changes

Cohort / File(s) Summary
Controller 계층 분리 및 엔드포인트 재정의
src/main/java/.../home/controller/HomeController.java, src/main/java/.../home/controller/HomeControllerImpl.java
Controller를 인터페이스/구현으로 분리. 생성/조회/목록/검색/수정/삭제/상태변경/내매물/좋아요/시설 관련 엔드포인트 추가 및 시그니처(IDs Integer, DTOs, multipart/form-data) 변경. Authentication 파라미터 사용 방식 변경.
Service 인터페이스/구현 리뉴얼
src/main/java/.../home/service/HomeService.java, src/main/java/.../home/service/HomeServiceImpl.java
DTO 중심 API로 전환. create/update에서 이미지(List) 처리(S3 연동), 검색/카운트/좋아요 토글·조회, 시설 조회, 상태 변경 등 비즈니스 메서드 재정의 및 예외/트랜잭션 갱신.
DTO 재구성(요청/응답/검색/수정)
src/main/java/.../home/dto/*, src/main/java/.../home/dto/request/*, src/main/java/.../home/dto/response/*
HomeCreateDTO, HomeCreateRequestDto, HomeResponseDTO, HomeSearchDTO, HomeUpdateRequestDto 등 신규 DTO 추가 및 기존 DTO(예: HomeCreateRequestDto old 위치, HomeResponseDto 등) 삭제/교체. 내부 MaintenanceFee DTO, 이미지 Multipart 지원 등 구조 변경.
VO 신설 및 기존 VO 제거
src/main/java/.../home/vo/*
HomeVO, HomeDetailVO, HomeImageVO, HomeFacilityVO, HomeMaintenanceFeeVO, HomeLikeVO, FacilityCategory, FacilityItem 등 VO 추가. 기존 HomeRegisterVO 등 레거시 VO 삭제.
Mapper 인터페이스 및 SQL(XML) 전면 개편
src/main/java/.../home/mapper/HomeMapper.java, src/main/resources/.../home/mapper/HomeMapper.xml
Mapper 시그니처를 VO/DTO 기반으로 교체(매개/반환 Integer/VO), insert/select/update/delete/검색/좋아요/시설/관리비용 SQL 재작성, LIMIT/OFFSET 적용 및 keyProperty 변경.
Enum / Config 변경
src/main/java/.../home/enums/HomeDirection.java, .../HomeStatus.java, src/main/java/.../global/config/ServletConfig.java
HomeDirection/HomeStatus에 getKey() 추가, HomeStatus 상수명 변경(SOLD→CONTRACTED). ServletConfig에 LocalDate 변환기 등록(폼 바인딩).
영향받는 서비스 연동 변경
src/main/java/.../fraud/service/AiFraudAnalyzerService.java
HomeRegisterVO → HomeVO 호출 경로로 변경 및 조회 예외 처리 방식 변경(Optional.orElseThrow → try/catch 기반).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HomeControllerImpl
  participant HomeServiceImpl
  participant HomeMapper
  participant S3Service

  Client->>HomeControllerImpl: POST /api/homes (multipart) HomeCreateRequestDto + images
  HomeControllerImpl->>HomeServiceImpl: createHome(createDTO, images, userId)
  HomeServiceImpl->>HomeMapper: insertHome / insertHomeDetail
  HomeServiceImpl->>HomeMapper: insertHomeFacility / insertHomeMaintenanceFee
  HomeServiceImpl->>S3Service: upload images
  S3Service-->>HomeServiceImpl: image URLs
  HomeServiceImpl->>HomeMapper: insertHomeImage (URLs)
  HomeServiceImpl-->>HomeControllerImpl: created homeId
  HomeControllerImpl-->>Client: ApiResponse<Integer>
Loading
sequenceDiagram
  participant Client
  participant HomeControllerImpl
  participant HomeServiceImpl
  participant HomeMapper

  Client->>HomeControllerImpl: GET /api/homes/{homeId}
  HomeControllerImpl->>HomeServiceImpl: getHome(homeId)
  HomeServiceImpl->>HomeMapper: selectHomeById / selectHomeDetailByHomeId
  HomeServiceImpl->>HomeMapper: selectHomeImagesByHomeId / selectHomeFacilitiesByHomeDetailId / selectHomeMaintenanceFeesByHomeId
  HomeServiceImpl-->>HomeControllerImpl: HomeResponseDTO
  HomeControllerImpl-->>Client: ApiResponse<HomeResponseDTO>
Loading
sequenceDiagram
  participant Client
  participant HomeControllerImpl
  participant HomeServiceImpl
  participant HomeMapper

  Client->>HomeControllerImpl: POST /api/homes/{homeId}/like
  HomeControllerImpl->>HomeServiceImpl: toggleHomeLike(userId, homeId)
  HomeServiceImpl->>HomeMapper: selectHomeLikeExists(userId, homeId)
  alt exists
    HomeServiceImpl->>HomeMapper: deleteHomeLike(userId, homeId)
  else
    HomeServiceImpl->>HomeMapper: insertHomeLike(homeLikeVO)
  end
  HomeServiceImpl-->>HomeControllerImpl: Void
  HomeControllerImpl-->>Client: ApiResponse<Void>
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Assessment against linked issues

Objective Addressed Explanation
매물 등록 기능 구현 (#77)
매물 조회 기능 구현 (#77)
매물 리스트 기능 구현 (#77)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
좋아요 토글/조회 추가 (src/main/java/.../home/controller/HomeController*.java, .../service/*, .../mapper/*, .../vo/HomeLikeVO.java) #77 체크리스트는 등록/조회/리스트만 명시; 좋아요 관련 기능은 이슈 요구에 포함되어 있지 않음.
시설 카테고리·아이템 조회 추가 (.../vo/FacilityCategory.java, .../vo/FacilityItem.java, 매퍼/컨트롤러/서비스 변경) #77 요구 범위는 매물 등록/조회/목록에 집중되어 있으며 시설 메타데이터 조회는 별도 확장으로 보임.
상태 변경 API 추가 (PATCH /{homeId}/status) 이슈 체크리스트에 상태 변경 항목이 없음.
검색 조건·검색 API 추가 (HomeSearchDTO, GET /search) 이슈에는 기본 리스트만 요구; 고급 검색은 확장 기능임.
전역 LocalDate 컨버터 추가 (ServletConfig.addFormatters) 글로벌 폼 바인딩 설정 변경은 기능 요구(#77)에 직접 명시되지 않음.

Possibly related PRs

Suggested reviewers

  • MeongW
  • Whatdoyumin

Poem

"깡총깡총, 당근 들고 왔네 🥕
DTO 옷 갈아입고 VO 씨앗 뿌려,
사진은 S3에 맡기고 탭 한 번,
좋아요도 콕, 검색도 쏙—
오늘도 코드밭엔 귀여운 버그만 없기를!"

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/homeCreate

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 34

🔭 Outside diff range comments (13)
src/main/java/org/scoula/domain/home/enums/HomeStatus.java (1)

26-39: from(String)에서 이름/옛 상수명(SOLD)도 허용해 역직렬화 유연성 확보 권장

현재는 한글 설명만 매칭합니다. enum 이름(AVAILABLE/RESERVED/CONTRACTED)도 허용하고, 과거 상수명 SOLD를 CONTRACTED로 매핑하면 하위호환성에 유리합니다.

적용 제안 diff:

       @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
       public static HomeStatus from(String value) {
           if (value == null || value.trim().isEmpty()) {
               throw new IllegalArgumentException("매물 상태 값이 null이거나 비어있습니다.");
           }

           String trimmedValue = value.trim();
-          for (HomeStatus status : values()) {
-              if (status.description.equals(trimmedValue)) {
-                  return status;
-              }
-          }
+          for (HomeStatus status : values()) {
+              // 한글 설명 또는 enum 이름(대소문자 무시) 모두 허용
+              if (status.description.equals(trimmedValue) || status.name().equalsIgnoreCase(trimmedValue)) {
+                  return status;
+              }
+          }
+          // 하위호환: SOLD -> CONTRACTED 매핑
+          if ("SOLD".equalsIgnoreCase(trimmedValue)) {
+              return CONTRACTED;
+          }
           throw new IllegalArgumentException("알 수 없는 매물 상태입니다: " + value);
       }
src/main/java/org/scoula/domain/chat/service/ChatServiceImpl.java (4)

449-453: findExistingChatRoom에서 NullPointerException 발생 가능성

findByUserAndHome가 null을 반환할 수 있는데, 후속에서 즉시 getChatRoomId()를 호출하고 있습니다. NPE 방지 로직이 필요합니다.

   @Override
   public Long findExistingChatRoom(Long ownerId, Long buyerId, Long propertyId) {
       ChatRoom existingRoom = chatRoomMapper.findByUserAndHome(ownerId, buyerId, propertyId);
-      return existingRoom.getChatRoomId();
+      return (existingRoom != null) ? existingRoom.getChatRoomId() : null;
   }

571-579: S3 키 추출 로직 정규식 버그 및 취약한 파싱

String.split(".amazonaws.com/")는 정규식에서 .이 모든 문자를 의미해 오작동합니다. 또한 쿼리스트링이 포함된 URL을 안정적으로 처리하지 못합니다. URI 파서를 사용해 안전하게 키를 추출하세요.

-  private String extractS3KeyFromUrl(String fileUrl) {
-      try {
-          if (fileUrl.contains(".amazonaws.com/")) {
-              String[] parts = fileUrl.split(".amazonaws.com/");
-              if (parts.length > 1) {
-                  return parts[1];
-              }
-          }
-          return null;
-      } catch (Exception e) {
-          return null;
-      }
-  }
+  private String extractS3KeyFromUrl(String fileUrl) {
+      try {
+          java.net.URI uri = java.net.URI.create(fileUrl);
+          String host = uri.getHost();
+          String path = uri.getPath(); // leading '/'
+          if (host != null && host.contains("amazonaws.com") && path != null && path.length() > 1) {
+              return path.startsWith("/") ? path.substring(1) : path;
+          }
+          return null;
+      } catch (Exception e) {
+          return null;
+      }
+  }

추가 import가 필요합니다:

import java.net.URI;

538-545: 파일명에 쿼리스트링 포함 가능성 처리

S3 서명 URL 등의 경우 파일명에 ? 이하 쿼리스트링이 포함되어 반환됩니다. 화면 표시용 파일명은 쿼리 제거가 필요합니다.

-  String fileUrl = message.getFileUrl();
-  String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
+  String fileUrl = message.getFileUrl();
+  String rawFileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
+  String fileName = rawFileName.contains("?")
+          ? rawFileName.substring(0, rawFileName.indexOf('?'))
+          : rawFileName;
   String fileType = determineFileTypeFromUrl(fileUrl);

708-711: Redis 키 만료(Expiration) 미설정으로 영구 잠김 위험

계약 요청 키에 TTL이 없어 예외 상황(알림 손실 등)에서 영구적으로 새로운 요청을 막을 수 있습니다. 보수적으로 TTL을 부여하세요(예: 24시간).

-  stringRedisTemplate.opsForValue().set(key, value);
+  stringRedisTemplate.opsForValue().set(key, value, java.time.Duration.ofHours(24));

필요 시 import:

import java.time.Duration;
src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java (2)

82-136: 필드명 불일치 문제 해결 필요

코드 주석에서도 언급되었듯이 DTO와 VO 간 필드명이 일치하지 않는 부분들이 있습니다 (예: getBathroomCnt() → bathroomCount, getIsParking() → isParkingAvailable). 이는 유지보수성을 저하시킬 수 있습니다.

필드명을 일관성 있게 통일하거나, 매핑 로직을 별도의 mapper 클래스로 분리하는 것을 고려해보세요. mapper 클래스 생성을 도와드릴까요?


199-205: parseHomeDirection 메서드 사용되지 않음 — 삭제 검토

프로젝트 전역 검색 결과 선언부(src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java)만 발견되어 호출처가 없습니다. 불필요하면 제거하세요.

  • 파일: src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java (약 199-205행)

삭제 제안(diff):

  •  private static HomeDirection parseHomeDirection(String direction) {
    
  •      try {
    
  •          return HomeDirection.valueOf(direction);
    
  •      } catch (IllegalArgumentException e) {
    
  •          return null;
    
  •      }
    
  •  }
    
src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java (1)

101-118: homeDirection 응답 불일치 — enum 직렬화 방식 통일 필요

HomeResponseDto(응답 DTO, String 필드)는 vo.getHomeDirection().name()으로 "S/E/..." 형태를 반환하는 반면, 다른 DTO인 src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java는 HomeDirection(enum) 타입 필드(직렬화 시 @jsonvalue로 getDescription() => "남향" 등 반환)를 사용합니다. 즉, API 응답이 DTO마다 불일치합니다.

수정 권장 위치:

  • src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java — (약줄 103)
    • 현재: .homeDirection(vo.getHomeDirection() != null ? vo.getHomeDirection().name() : null)
    • 권장: .homeDirection(vo.getHomeDirection() != null ? vo.getHomeDirection().getDescription() : null)
  • src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java — (약줄 49)
    • 현재 필드: private HomeDirection homeDirection; → 다른 DTO와의 의도된 스펙(키 vs 설명) 확인 필요
  • 참조: src/main/java/org/scoula/domain/home/enums/HomeDirection.java — @jsonvalue로 getDescription() 사용, getKey()로 name 반환 가능

권장 방안 (간단):

  • API 통일: 둘 다 description("남향" 등)으로 통일 → HomeResponseDto에서 .getDescription() 사용
  • 하위호환 필요 시: key/description 이중 필드 추가 (예: homeDirectionKey, homeDirectionDescription) 후 점진적 마이그레이션

주의: 현재 변경은 기존 클라이언트(문자열 키 "S"/"E" 등)와의 호환성에 영향을 줄 수 있으니 변경 전 영향 범위 검토 요청드립니다.

src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java (3)

251-285: 반환 타입 불일치: 서비스는 boolean, 컨트롤러 제네릭은 ClauseImproveResponseDto

setEndPointAndExport는 서비스 시그니처가 boolean 반환인데, 컨트롤러는 ApiResponse<ClauseImproveResponseDto>로 선언하고 빈 success()를 반환합니다. 컴파일/런타임 혼선을 초래합니다. 반환 제네릭을 Boolean으로 수정하고 실제 결과를 반환하세요.

-      @PostMapping("/{contractChatId}/end-point-export")
-      public ResponseEntity<ApiResponse<ClauseImproveResponseDto>> setEndPointAndExport(
+      @PostMapping("/{contractChatId}/end-point-export")
+      public ResponseEntity<ApiResponse<Boolean>> setEndPointAndExport(
               @PathVariable Long contractChatId,
               @RequestParam Long order,
               Authentication authentication) {
           try {
               ...
-              boolean importClause =
-                      contractChatService.setEndPointAndExport(contractChatId, userId, order);
-              return ResponseEntity.ok(ApiResponse.success());
+              boolean importClause =
+                      contractChatService.setEndPointAndExport(contractChatId, userId, order);
+              return ResponseEntity.ok(ApiResponse.success(importClause, "특약 대화 내보내기 완료"));
           } catch (Exception e) {
               ...
           }
       }

412-414: Long 비교에서 '==' 사용은 레퍼런스 비교 버그를 유발합니다

userId == contractChat.getOwnerId()는 Long 레퍼런스를 비교하여 잘못된 역할 문자열이 설정될 수 있습니다. .equals로 수정하세요.

-              String role = userId == contractChat.getOwnerId() ? "임대인입니다" : "임차인입니다";
+              String role = userId.equals(contractChat.getOwnerId()) ? "임대인입니다" : "임차인입니다";

62-63: RedisTemplate 필드 주입(@Autowired) 제거하고 생성자 주입으로 전환

다른 의존성은 생성자 주입인데 stringRedisTemplate만 필드 주입입니다. 일관성과 테스트 용이성을 위해 생성자 주입으로 통일하세요.

-      public ContractChatControllerImpl(
+      public ContractChatControllerImpl(
               ContractChatServiceInterface contractChatService,
               UserServiceInterface userService,
               ContractChatMapper contractChatMapper,
               SimpMessagingTemplate messagingTemplate,
               PreContractDataService preContractDataService,
-              SpecialContractMongoRepository specialContractMongoRepository) {
+              SpecialContractMongoRepository specialContractMongoRepository,
+              RedisTemplate<String, String> stringRedisTemplate) {
           this.contractChatService = contractChatService;
           this.userService = userService;
           this.contractChatMapper = contractChatMapper;
           this.messagingTemplate = messagingTemplate;
           this.preContractDataService = preContractDataService;
           this.specialContractMongoRepository = specialContractMongoRepository;
+          this.stringRedisTemplate = stringRedisTemplate;
       }
 
-      @Autowired private RedisTemplate<String, String> stringRedisTemplate;
+      private final RedisTemplate<String, String> stringRedisTemplate;

Also applies to: 47-60

src/main/java/org/scoula/domain/chat/service/ContractChatServiceImpl.java (2)

577-585: 특약 종료 요청 Redis 키에 TTL 미설정 — 고아 키 누적 위험

종료 요청 키(contract:request-end:)에 만료시간이 없어, 응답 누락 시 영구적으로 남을 수 있습니다. 다른 플로우(수정/삭제)는 24시간 TTL을 적용하고 있어 일관성도 맞추는 편이 좋습니다.

-          stringRedisTemplate.opsForValue().set(key, value);
+          stringRedisTemplate.opsForValue().set(key, value, Duration.ofHours(24));

598-606: 종료 요청 거절 알림의 수신자(receiverId) 지정 오류

거절자는 임차인(userId)이며, 수신자는 임대인이어야 합니다. 현재 receiverId를 buyerId로 설정해 본인에게 전송되는 문제가 있습니다.

-                          .receiverId(contractChat.getBuyerId())
+                          .receiverId(contractChat.getOwnerId())
🧹 Nitpick comments (49)
src/main/java/org/scoula/domain/home/enums/HomeStatus.java (1)

22-24: getKey 추가는 적절하나 JSON 직렬화에는 노출되지 않습니다

현재 @jsonvalue가 getDescription()에 선언되어 있어 응답 JSON은 한글 설명 문자열만 내려갑니다. 외부 API에서 key와 description을 함께 노출하려면 DTO 레벨에서 (key, description) 구조를 명시적으로 반환하거나, 커스텀 직렬화를 도입해야 합니다.

이 API의 응답 스펙이 description-only가 맞는지 확인 부탁드립니다. 기존 클라이언트가 enum key를 기대했다면 응답 파괴적 변경일 수 있습니다.

src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)

5-11: Lombok 중복(@DaTa + @Getter/@Setter) 제거

@DaTa가 getter/setter/toString/equals/hashCode를 이미 생성합니다. @Getter/@Setter는 중복이므로 제거해 간결성을 높이세요.

아래 diff 적용 제안:

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public class HomeMaintenanceFeeVO {
src/main/java/org/scoula/global/config/ServletConfig.java (1)

80-93: LocalDate 변환기 예외 처리로 바인딩 오류 메시지 개선 제안

현재 잘못된 날짜 문자열이 들어오면 DateTimeParseException이 그대로 전파됩니다. 사용자 친화적인 400 응답 메시지를 위해 명시적 예외 전환을 권장합니다.

아래 diff 적용 제안:

       @Override
       public void addFormatters(FormatterRegistry registry) {
           // LocalDate converter for form data
           registry.addConverter(
                   new Converter<String, LocalDate>() {
                       @Override
                       public LocalDate convert(String source) {
                           if (source == null || source.trim().isEmpty()) {
                               return null;
                           }
-                          return LocalDate.parse(source, DateTimeFormatter.ISO_LOCAL_DATE);
+                          String s = source.trim();
+                          try {
+                              return LocalDate.parse(s, DateTimeFormatter.ISO_LOCAL_DATE);
+                          } catch (java.time.format.DateTimeParseException e) {
+                              // 명확한 400 원인 전달을 위해 IllegalArgumentException으로 변환
+                              throw new IllegalArgumentException("유효하지 않은 날짜 형식입니다(yyyy-MM-dd): " + source, e);
+                          }
                       }
                   });
       }

추가로 다음 import가 필요합니다(선택):

import java.time.format.DateTimeParseException;
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)

7-12: Lombok 중복(@DaTa + @Getter/@Setter) 제거

@DaTa만 유지하고 @Getter/@Setter는 제거하는 것이 간결하고 일관적입니다(다른 VO들과 함께 정리 권장).

적용 제안 diff:

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public class HomeLikeVO {
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)

5-11: Lombok 중복(@DaTa + @Getter/@Setter) 제거

여기도 @DaTa로 충분합니다. 일관된 스타일로 정리해 주세요.

적용 제안 diff:

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public class FacilityItem {
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)

5-10: Lombok 애노테이션 중복 제거 제안 (@DaTa + @Getter/@Setter 동시 사용)

@DaTa가 이미 getter/setter, toString, equals/hashCode를 생성하므로 @Getter/@Setter는 중복입니다. 불필요한 중복을 제거해 가독성과 유지보수성을 높이는 것이 좋습니다.

아래처럼 정리하는 것을 권장합니다.

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
 public class HomeImageVO {
src/main/java/org/scoula/domain/chat/service/ChatServiceImpl.java (2)

634-651: 채팅방/계약채팅방 식별자 맵(userCurrentChatRoom) 혼용 가능성

userCurrentChatRoom(Long→Long)에 일반 채팅방 ID와 계약 채팅방 ID를 동일 공간에 저장/비교하는 것으로 보입니다. 두 ID 공간이 충돌할 경우 잘못된 접근 허용/차단 이슈가 발생할 수 있습니다. 별도 맵 분리 또는 타입 구분(래퍼) 도입을 권장합니다.

분리 예시:

  • Map<Long, Long> userCurrentChatRoom
  • Map<Long, Long> userCurrentContractChatRoom

원하시면 헬퍼 메서드/상태 객체로 리팩터링 제안 드리겠습니다.

Also applies to: 292-301


285-289: private 메서드에 {@inheritdoc} 사용은 적절치 않음

상속/인터페이스 계약이 없는 private 메서드에는 {@inheritdoc}이 무의미합니다. 주석 제거를 권장합니다.

-  /** {@inheritDoc} */
   private boolean isUserInCurrentChatRoom(Long userId, Long chatRoomId) {
src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java (2)

6-6: 사용되지 않는 import 제거 필요

javax.validation.constraints.* 가 import되어 있지만 실제로 validation 어노테이션이 사용되지 않고 있습니다.

-import javax.validation.constraints.*;
-

30-31: 필수 필드에 validation 추가 검토

API 문서에서 required = true로 표시된 필드들(addr1, residenceType, leaseType, depositPrice)에 대해 실제 validation 어노테이션(@NotNull, @notblank 등)이 없습니다. 런타임 검증을 위해 추가를 고려해보세요.

+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;

     @ApiModelProperty(value = "시/도 및 시/군/구", example = "서울특별시 강남구", required = true)
+    @NotBlank(message = "주소는 필수입니다")
     private String addr1;

     @ApiModelProperty(value = "보증금 (원)", example = "300000000", required = true)
+    @NotNull(message = "보증금은 필수입니다")
+    @PositiveOrZero(message = "보증금은 0 이상이어야 합니다")
     private Integer depositPrice;

Also applies to: 50-51

src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)

5-10: 중복된 Lombok 어노테이션 제거 필요

@DaTa 어노테이션이 이미 @Getter, @Setter, @tostring, @EqualsAndHashCode, @requiredargsconstructor를 포함하므로, @Getter@Setter는 중복입니다.

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder
src/main/java/org/scoula/domain/chat/dto/ModificationRequestData.java (1)

16-16: createdAt 필드 타입 재검토 필요

createdAt이 String 타입으로 정의되어 있는데, 날짜/시간 처리의 일관성과 타입 안정성을 위해 LocalDateTime이나 Instant 사용을 고려해보세요.

+import java.time.LocalDateTime;

-    private String createdAt;
+    private LocalDateTime createdAt;
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationResponseDto.java (1)

13-13: primitive boolean 사용 재고려

accepted 필드가 primitive boolean으로 정의되어 있어 null 값을 표현할 수 없습니다. API 응답에서 명시적인 null 상태가 필요한 경우 Boolean wrapper 타입 사용을 고려해보세요.

-    private boolean accepted;
+    private Boolean accepted;
src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java (2)

140-141: 사용되지 않는 변수 제거

parsedBuildDate 변수가 선언되었지만 사용되지 않습니다.

-        LocalDateTime parsedBuildDate =
-                dto.getBuildDate() != null ? dto.getBuildDate().atStartOfDay() : null;
-

121-121: supplyArea 기본값 설정의 일관성

supplyArea가 null일 때 0f로 기본값을 설정하는데, 다른 nullable 필드들과 처리 방식이 다릅니다. 일관된 null 처리 정책이 필요합니다.

비즈니스 요구사항에 따라 null을 허용하거나, 모든 숫자 필드에 대해 일관된 기본값 정책을 적용하는 것을 고려해보세요.

Also applies to: 181-181

src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java (1)

106-106: 불필요한 null 체크/삼항 연산자 제거 제안

빌더는 null을 그대로 받으므로, 현재 표현은冗長합니다.

-                  .buildDate(vo.getBuildDate() != null ? vo.getBuildDate() : null)
+                  .buildDate(vo.getBuildDate())
src/main/java/org/scoula/domain/home/enums/HomeDirection.java (2)

27-29: getKey() 추가는 유용합니다만, JSON 직렬화 시 자동 노출되지는 않습니다

현재 @jsonvalue가 getDescription()에 달려 있어 enum 자체 직렬화 시 description만 노출됩니다. getKey()는 DTO에서 명시적으로 호출/매핑해야 합니다. 프런트에 key와 description을 모두 제공하려면 DTO에 전용 필드를 추가하거나 커스텀 직렬화를 고려하세요.


31-39: 입력 역직렬화 유연성 강화 제안(from 메서드가 설명만 받는 제약)

from(String)은 description(한글)만 처리합니다. API 클라이언트가 enum 키(예: “S”)를 보낼 수도 있으니 키도 수용하도록 보완을 권장합니다.

아래처럼 보조 팩토리를 추가해 키/설명을 모두 허용할 수 있습니다(변경은 참고용 코드 스니펫).

// 참고: 기존 메서드는 유지
public static HomeDirection fromKey(String key) {
    for (HomeDirection d : HomeDirection.values()) {
        if (d.name().equalsIgnoreCase(key)) return d;
    }
    throw new IllegalArgumentException("Unknown home direction key: " + key);
}

public static HomeDirection fromFlexible(String value) {
    // key 우선 시도
    for (HomeDirection d : HomeDirection.values()) {
        if (d.name().equalsIgnoreCase(value)) return d;
    }
    // description 시도
    for (HomeDirection d : HomeDirection.values()) {
        if (d.getDescription().equals(value)) return d;
    }
    throw new IllegalArgumentException("Unknown home direction: " + value);
}
src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (1)

37-39: 새 필드(area, landCategory) 추가 OK — 유효성 및 도메인 제약 보강 제안

등록 요청 DTO에 수용된 것은 좋습니다. 다만 다음을 고려해 주세요.

  • 유효성: 면적은 음수가 될 수 없으므로 @PositiveOrZero 또는 @DecimalMin("0.0") 부착을 권장합니다.
  • 도메인 제약: landCategory 값이 정해진 집합이라면 enum으로 치환하여 오입력 방지.
  • 서비스/매퍼 매핑: HomeDetailVO.area, landCategory로 정확히 매핑되는지 확인 필요.

예시(검증 어노테이션 추가; Jakarta 사용 시):

+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
@@
-      private Float area;
-      private String landCategory;
+      @NotNull
+      @DecimalMin(value = "0.0", inclusive = true)
+      private Float area;
+      private String landCategory;

추가로, 동일 DTO에 images와 imageFiles가 모두 MultipartFile 리스트로 존재합니다(라인 63-65). 목적이 다르지 않다면 하나로 통합하는 편이 명확합니다.

src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)

5-10: Lombok 중복 제거

@DaTa는 @Getter/@Setter를 포함합니다. 중복 어노테이션은 제거해도 됩니다.

-@Data
-@Getter
-@Setter
+@Data
src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java (2)

58-61: createdAt/updatedAt 타입 정책 재검토(LocalDate vs LocalDateTime)

생성/수정 시각이 날짜만 필요한 요구인지 확인 바랍니다. 일반적으로는 LocalDateTime(또는 Instant)을 사용합니다. 현재 다른 파일(HomeResponseDto)에서는 createdAt을 String으로 수동 포맷팅하고 있어 불일치가 발생합니다.

정책 제안:

  • 내부: LocalDateTime 유지
  • 외부: @jsonformat(pattern="yyyy-MM-dd'T'HH:mm:ss") 등으로 일관된 직렬화
  • 과거 응답과의 호환성이 필요하면 별도 버전 필드 또는 어댑터로 대응

49-50: HomeDirection의 직렬화 결과 확인 필요(@jsonvalue 영향)

HomeDirection에는 @jsonvalue가 getDescription()에 설정돼 있어 응답에 한글 설명이 나갑니다. 키(예: “S”)도 필요하면 key 필드를 추가로 노출하거나 DTO 변환계층에서 key/description을 동시에 제공하세요.

src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (3)

9-14: Lombok 중복 사용 제거 제안 (@DaTa만으로 충분)

@DaTa는 자동으로 getter/setter/toString/equals/hashCode를 생성합니다. @Getter/@Setter는 중복이며 유지보수 시 혼란을 줄 수 있습니다.

적용 diff:

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder

22-22: bathroomCnt 네이밍 일관성 검토 필요

요청 DTO(HomeCreateRequestDto)에선 bathroomCount, VO/DTO(HomeDetailVO/HomeCreateDTO)에는 bathroomCnt로 표기되어 있습니다. 계층 간 네이밍 불일치는 매핑 실수/버그의 원인이 됩니다. 하나로 통일하는 것을 권장합니다.

원하시면 프로젝트 전역에서 bathroomCnt/bathroomCount 사용처를 스캔하고 정규화 패치를 제안드리겠습니다.


25-26: 면적 타입의 정밀도 검토 (Float → BigDecimal/Double 고려)

면적 데이터는 소수 정밀도가 중요할 수 있습니다. Float(단정도) 대신 Double 또는 BigDecimal 사용을 고려하세요. DB 매핑 및 응답 DTO와의 정합성 확인이 필요합니다.

src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java (1)

34-36: 필터 명세 범위 일관성 개선 제안

exclusiveArea는 단일 값인데 supplyArea와 floor에는 min/max 범위가 있습니다. 검색 UX/쿼리 일관성을 위해 minExclusiveArea/maxExclusiveArea 도입을 고려하세요.

src/main/java/org/scoula/domain/home/vo/HomeVO.java (2)

11-16: Lombok 중복 사용 제거 제안 (@DaTa만으로 충분)

@DaTa만 유지하고 @Getter/@Setter는 제거하는 것이 간결합니다.

적용 diff:

 @Data
-@Getter
-@Setter
 @AllArgsConstructor
 @NoArgsConstructor
 @Builder

33-34: 생성/수정 시각 타입 재검토 (LocalDate → LocalDateTime 권장)

시간 정보가 필요한 경우가 많습니다. LocalDateTime으로 확장하면 정렬/필터링/감사로그에 유리합니다. DB 컬럼 및 매퍼 수정 동반 고려.

src/main/java/org/scoula/domain/chat/controller/ContractChatController.java (2)

160-165: PathVariable/네이밍/타입 정합성 점검: clauseOrder vs order, Integer vs Long

특약 관련 기존 엔드포인트 일부는 order(Long), 본 블록은 clauseOrder(Integer)를 사용합니다. 클라이언트/서비스 계층에서 혼동을 줄이려면 동일한 이름과 타입으로 통일을 검토하세요.


10-10: 와일드카드(import org.scoula.domain.chat.dto.*) 사용은 유지보수성에 불리할 수 있음

필요 타입만 명시적 import를 권장합니다. IDE 자동정리 규칙과 코드 스타일 가이드에 맞추어 조정하세요.

src/main/java/org/scoula/domain/home/service/HomeService.java (4)

14-17: Service 계층이 웹 타입(MultipartFile)에 의존 — 계층 분리 권장

Service 인터페이스가 MultipartFile에 의존하면 웹 계층과 결합됩니다. 파일 업로드 처리는 Controller/Facade에서 수행하고, Service에는 저장된 파일 식별자/URL 혹은 도메인 VO(예: ImageMeta)를 전달하는 설계를 권장합니다.

예) List<String> imageUrls 또는 List<ImageMeta> 등으로 대체.


27-30: updateHome 시그니처도 동일하게 웹 타입 의존 — 동일 개선 필요

위와 동일한 이유로 Service의 MultipartFile 의존 제거를 권장드립니다.


21-23: 페이징 응답 모델 개선 제안

getHomeList(int page, int size)가 List만 반환하면 총건수/다음 페이지 여부를 알기 어렵습니다. PagedResponse<T> 같은 래퍼를 통해 total/hasNext/offset 등을 포함하는 구조를 고려하세요.


53-56: 찜 토글 반환값 개선: 상태 불명확성 해소

현재 void 반환은 호출 후 상태 확인이 필요합니다. 토글 결과(찜됨/해제됨)를 boolean/enum으로 반환하는 것을 권장합니다.

예)

-      void toggleHomeLike(Integer userId, Integer homeId);
+      boolean toggleHomeLike(Integer userId, Integer homeId);
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (1)

39-46: 수치형 면적 타입으로 Float 사용 시 정밀도 이슈 가능성

금액/면적으로 사용되는 필드에 Float를 사용하면 정밀도 손실 가능성이 큽니다. 금액은 Integer로 유지 중이지만, 면적은 차후 연산/필터링 시 반올림 문제가 생길 수 있습니다. BigDecimal 또는 Double 사용을 고려해 주세요.

Also applies to: 49-51, 60-63

src/main/java/org/scoula/domain/chat/service/ContractChatServiceInterface.java (3)

78-91: Javadoc과 반환 타입 불일치(setEndPointAndExport): 문서 갱신 필요

주석은 “대화 내용 목록 반환”으로 설명하지만 실제 반환 타입은 boolean입니다. API 계약 문서를 정확히 반영해 주세요.

-     * @return 시작점부터 종료점까지의 대화 내용 목록 (시간순 정렬)
+     * @return 종료점 설정 및 내보내기 처리 성공 여부

268-271: null 반환 대신 Optional 사용 고려

getPendingModificationRequest가 요청 부재 시 null을 반환하는 설계입니다. 호출부 NPE 리스크를 줄이기 위해 Optional<ModificationRequestData> 반환을 고려해 주세요.


281-289: 주문/조항 번호의 정수 타입 일관화 권장

동일 도메인에서 order는 Long, clauseOrder는 Integer로 혼재합니다. 일관된 스키마/DB 컬럼 타입과 맞춰 하나로 통일하는 것이 장기 유지보수에 유리합니다.

src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java (2)

41-47: 요청 DTO 바인딩 방식 일관화: @RequestPart → @ModelAttribute 권장

인터페이스는 @ModelAttribute를 사용하지만 구현체는 @RequestPart를 사용합니다. multipart/form-data에서 DTO+파일을 바인딩할 때 @ModelAttribute로도 정상 바인딩되므로 인터페이스와 일치시키는 것을 권장합니다.

-      public ResponseEntity<ApiResponse<Integer>> createHome(
-              @Valid @RequestPart HomeCreateRequestDto requestDto, Authentication authentication) {
+      public ResponseEntity<ApiResponse<Integer>> createHome(
+              @Valid @ModelAttribute HomeCreateRequestDto requestDto, Authentication authentication) {

301-319: Long → Integer 다운캐스팅 잠재 리스크

getCurrentUserId에서 LongInteger로 변환합니다. 사용자 ID 최대값이 int 범위를 넘지 않는다는 보장이 없다면, 서비스·리포지토리 전반적으로 Long 일관 사용을 고려하세요.

src/main/java/org/scoula/domain/home/controller/HomeController.java (2)

24-31: createHome 파라미터 바인딩 주석/구현 일치 여부 확인

인터페이스는 @ModelAttribute를 사용하고, 구현체는 현재 @RequestPart로 작성되어 있습니다(수정 제안 제출). 스웨거 문서 상에도 @ModelAttribute 기반으로 표현되므로, 구현체 정합성을 맞춰 주세요.


39-46: 응답 래퍼 표준화 고려(ApiResponse vs PageResponse)

가이드라인은 “컨트롤러 엔드포인트는 일관된 ApiResponse 래퍼를 반환”하도록 명시합니다. 현재 목록/검색 API만 PageResponse를 직접 반환합니다. 프로젝트 표준이 PageResponse 직반환이라면 문서/가이드라인 업데이트, 아니면 ApiResponse<PageResponse<...>> 형태로 감싸는 표준화 방안을 검토해 주세요.

Also applies to: 47-51, 87-99

src/main/java/org/scoula/domain/chat/service/ContractChatServiceImpl.java (3)

48-51: ObjectMapper는 빈 주입으로 교체 권장

직접 생성(new ObjectMapper()) 대신 Spring 빈을 주입받으면 공통 설정(모듈, 직렬화 옵션 등)을 일관되게 적용할 수 있습니다.

-      private final ObjectMapper objectMapper = new ObjectMapper();
+      private final ObjectMapper objectMapper;

필드 변경에 맞춰 생성자 주입(@requiredargsconstructor)로 빈이 주입되도록 ObjectMapper를 @bean으로 등록했는지 확인 부탁드립니다.


2226-2231: 확정 거절 알림 문구 부정확

확정 거절인데 “수정을 거절하였습니다.”로 표기되어 있습니다. 확정 프로세스에 맞는 문구로 변경하세요.
[ Suggestion: 문구 변경 ]

-          AiMessage(contractChatId, "임차인이 수정을 거절하였습니다.");
+          AiMessage(contractChatId, "임차인이 최종 특약 확정을 거절했습니다.");

122-171: AI 메시지 발신자 ID 매직 넘버 상수화 제안

9999L/9998L/9997L 매직 넘버는 상수로 추출하면 가독성과 유지보수성이 향상됩니다.

+      private static final long SENDER_AI_DEFAULT = 9999L;
+      private static final long SENDER_AI_NEXT = 9997L;
+      private static final long SENDER_AI_BUTTON = 9998L;
...
-          final Long ai = 9999L;
+          final Long ai = SENDER_AI_DEFAULT;
...
-          final Long ai = 9997L;
+          final Long ai = SENDER_AI_NEXT;
...
-          final Long ai = 9998L;
+          final Long ai = SENDER_AI_BUTTON;

Also applies to: 139-154, 156-171

src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java (1)

23-27: 서비스 인터페이스 네이밍 가이드 위반(사소하지만 통일 필요)

가이드에 따르면 서비스는 *ServiceInterface / *ServiceImpl 페어를 사용해야 합니다. 현재 HomeServiceImpl이 HomeService를 구현하고 있습니다. 인터페이스명을 HomeServiceInterface로 정렬하는 것을 권장합니다.

Also applies to: 27-27

src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (2)

63-69: 컬럼 별칭/카멜케이스 변환 혼용

mapUnderscoreToCamelCase 설정이 없으면 home_direction → homeDirection 매핑이 실패할 수 있습니다. 현재 쿼리에서는 일부 컬럼만 별칭을 사용하고 있습니다. 명시적 별칭 사용으로 일관성을 맞추는 것을 권장합니다.


217-226: selectHomeLikeExists COUNT(*) 최적화 제안

존재 여부 확인은 COUNT(*) 대신 EXISTS가 더 효율적일 수 있습니다. 대용량 환경일수록 성능 이점이 있습니다.

SELECT CASE WHEN EXISTS (
  SELECT 1 FROM home_like WHERE user_id = #{userId} AND home_id = #{homeId}
) THEN 1 ELSE 0 END
src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1)

93-105: 찜 카운트 동기화용 Mapper 메서드 제안

home.like_cnt를 사용하는 경우, 찜 토글 시 증가/감소 전용 메서드를 추가해 데이터 정합성을 유지하세요.

예시(인터페이스):

int incrementHomeLikeCount(@Param("homeId") Integer homeId);
int decrementHomeLikeCount(@Param("homeId") Integer homeId);

예시(XML):

<update id="incrementHomeLikeCount">
  UPDATE home SET like_cnt = like_cnt + 1 WHERE home_id = #{homeId}
</update>
<update id="decrementHomeLikeCount">
  UPDATE home SET like_cnt = like_cnt - 1 WHERE home_id = #{homeId} AND like_cnt > 0
</update>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 17b3035 and df28b50.

📒 Files selected for processing (35)
  • config-submodule (1 hunks)
  • src/main/java/org/scoula/domain/chat/controller/ContractChatController.java (2 hunks)
  • src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java (2 hunks)
  • src/main/java/org/scoula/domain/chat/dto/FinalContractModificationRequestDto.java (1 hunks)
  • src/main/java/org/scoula/domain/chat/dto/FinalContractModificationResponseDto.java (1 hunks)
  • src/main/java/org/scoula/domain/chat/dto/ModificationRequestData.java (1 hunks)
  • src/main/java/org/scoula/domain/chat/repository/SpecialContractMongoRepository.java (1 hunks)
  • src/main/java/org/scoula/domain/chat/service/ChatServiceImpl.java (1 hunks)
  • src/main/java/org/scoula/domain/chat/service/ContractChatServiceImpl.java (7 hunks)
  • src/main/java/org/scoula/domain/chat/service/ContractChatServiceInterface.java (2 hunks)
  • src/main/java/org/scoula/domain/home/controller/HomeController.java (1 hunks)
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (1 hunks)
  • src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java (1 hunks)
  • src/main/java/org/scoula/domain/home/enums/HomeDirection.java (1 hunks)
  • src/main/java/org/scoula/domain/home/enums/HomeStatus.java (2 hunks)
  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1 hunks)
  • src/main/java/org/scoula/domain/home/service/HomeService.java (1 hunks)
  • src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java (5 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeVO.java (1 hunks)
  • src/main/java/org/scoula/global/config/ServletConfig.java (2 hunks)
  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/main/java/org/scoula/domain/*/repository/*Repository.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

Place MongoDB repositories under src/main/java/org/scoula/domain/*/repository/*Repository.java

Files:

  • src/main/java/org/scoula/domain/chat/repository/SpecialContractMongoRepository.java
src/main/java/org/scoula/**/service/*Service{Interface,Impl}.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

Use interface-implementation pattern for services: *ServiceInterface.java (interface) and *ServiceImpl.java (implementation)

Files:

  • src/main/java/org/scoula/domain/chat/service/ChatServiceImpl.java
  • src/main/java/org/scoula/domain/chat/service/ContractChatServiceImpl.java
  • src/main/java/org/scoula/domain/chat/service/ContractChatServiceInterface.java
  • src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
src/main/java/org/scoula/**/controller/*Controller{,Impl}.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

Use interface-implementation pattern for controllers: *Controller.java (interface) and *ControllerImpl.java (implementation)

Files:

  • src/main/java/org/scoula/domain/chat/controller/ContractChatController.java
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
  • src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java
  • src/main/java/org/scoula/domain/home/controller/HomeController.java
src/main/java/org/scoula/**/controller/**/*.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/java/org/scoula/**/controller/**/*.java: All controller endpoints should return a consistent ApiResponse<T> wrapper
Add Swagger annotations to controller endpoints for API documentation

Files:

  • src/main/java/org/scoula/domain/chat/controller/ContractChatController.java
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
  • src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java
  • src/main/java/org/scoula/domain/home/controller/HomeController.java
src/main/java/org/scoula/domain/*/mapper/*Mapper.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/java/org/scoula/domain/*/mapper/*Mapper.java: Define MyBatis mapper interfaces under src/main/java/org/scoula/domain/*/mapper/*Mapper.java
Annotate MyBatis mapper interfaces with @Mapper

Files:

  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java
src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml: Place MyBatis XML mappers at src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml
Name MyBatis XML mappers *Mapper.xml to match their corresponding *Mapper.java interfaces

Files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
🧠 Learnings (4)
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/java/org/scoula/domain/*/mapper/*Mapper.java : Define MyBatis mapper interfaces under `src/main/java/org/scoula/domain/*/mapper/*Mapper.java`

Applied to files:

  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java
  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml : Name MyBatis XML mappers `*Mapper.xml` to match their corresponding `*Mapper.java` interfaces

Applied to files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml : Place MyBatis XML mappers at `src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml`

Applied to files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/java/org/scoula/domain/*/mapper/*Mapper.java : Annotate MyBatis mapper interfaces with `Mapper`

Applied to files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
🧬 Code Graph Analysis (19)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (6)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (1)
  • Data (17-66)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java (1)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java (2)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java (1)
  • Data (18-61)
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationResponseDto.java (2)
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationRequestDto.java (1)
  • Getter (5-15)
src/main/java/org/scoula/domain/chat/dto/ModificationRequestData.java (1)
  • Getter (5-17)
src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java (2)
src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java (1)
  • Data (13-62)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (7)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)
  • Data (7-17)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (8)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)
  • Data (7-17)
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationRequestDto.java (2)
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationResponseDto.java (1)
  • Getter (5-14)
src/main/java/org/scoula/domain/chat/dto/ModificationRequestData.java (1)
  • Getter (5-17)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (3)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (9)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (1)
  • Data (17-66)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)
  • Data (7-17)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (7)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java (3)
src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java (2)
  • ApiModel (20-116)
  • ApiModel (101-115)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (2)
  • Data (17-72)
  • Data (64-71)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/global/config/ServletConfig.java (1)
src/main/java/org/scoula/global/config/RootConfig.java (1)
  • Configuration (20-69)
src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java (7)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java (1)
  • Data (9-27)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeImageVO.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/FacilityItem.java (1)
  • Data (5-15)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)
  • Data (7-17)
src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java (1)
src/main/java/org/scoula/global/common/exception/BusinessException.java (1)
  • BusinessException (4-21)
src/main/java/org/scoula/domain/home/vo/FacilityCategory.java (1)
src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)
  • Data (5-14)
src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (3)
src/main/java/org/scoula/domain/home/vo/HomeVO.java (1)
  • Data (11-40)
src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (1)
  • Data (17-66)
src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java (1)
  • Data (18-61)
src/main/java/org/scoula/domain/home/controller/HomeController.java (1)
src/main/java/org/scoula/domain/chat/controller/AiClauseImproveTestController.java (1)
  • Api (24-151)
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java (1)
src/main/java/org/scoula/global/common/exception/BusinessException.java (1)
  • BusinessException (4-21)
🔇 Additional comments (17)
src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java (1)

14-17: likedAt 타입 검토(LocalDate vs LocalDateTime)

‘좋아요’ 시각을 일자 단위로만 관리해도 되는지 확인이 필요합니다. 시각까지 필요하다면 LocalDateTime이 적합합니다(정렬/중복 제약에도 유리).

현재 비즈니스/쿼리 요구사항(정렬, 최신순, 중복 방지)에 시:분:초가 필요한지 확인 부탁드립니다. 필요 시 필드 타입 및 매퍼/DDL 동시 변경이 필요합니다.

src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java (1)

1-128: 전체적으로 잘 구현되었습니다!

DTO 구조가 명확하고, Swagger 문서화도 적절히 되어 있으며, 매물 수정에 필요한 모든 필드가 포함되어 있습니다.

src/main/java/org/scoula/domain/chat/dto/ModificationRequestData.java (1)

5-17: DTO 구조가 잘 정의되었습니다

계약 수정 요청 데이터를 위한 필드들이 적절히 구성되어 있습니다.

src/main/java/org/scoula/domain/chat/dto/FinalContractModificationResponseDto.java (1)

5-14: DTO가 명확하게 정의되었습니다

최종 계약 수정 응답을 위한 필수 필드들이 적절히 포함되어 있습니다.

src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java (1)

76-81: createdAt 포맷팅: 우려 불필요 — createdAt은 LocalDateTime입니다

저장소 확인 결과 src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java에서 createdAt이 LocalDateTime으로 정의되어 있습니다. 따라서 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")로 포맷해도 UnsupportedTemporalTypeException이 발생하지 않습니다.

  • 확인 위치:
    • src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java — private LocalDateTime createdAt (약 45행)
    • src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java — 현재 포맷 코드(라인 ~76–81)

권장 사항(선택): 타입 안정성과 일관된 직렬화를 위해 DTO의 필드를 시간 타입(LocalDateTime 등)으로 유지하고 Jackson(@jsonformat 또는 전역 ObjectMapper 설정)에 직렬화를 위임하는 것이 좋습니다. 단기 패치 제안(문자열 변환)은 필수는 아닙니다.

Likely an incorrect or invalid review comment.

src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java (1)

12-14: ID 타입 일관성 확인

homeDetailId/itemId가 Integer인데, 연관 VO/테이블 컬럼이 모두 INT인지 확인 바랍니다. 다른 DTO에서는 Long을 쓰는 곳도 있으므로(예: HomeResponseDto.imageId 등) 도메인 전반의 ID 타입 정책을 통일하는 것을 권장합니다.

src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java (1)

50-53: 정렬 파라미터 화이트리스트 추가 필요 — MyBatis ${} 사용 여부 수동 확인 요청

sortBy / sortDirection이 MyBatis의 ${}로 그대로 들어가면 SQL 인젝션 위험이 있으므로, 허용값(화이트리스트) 반환 헬퍼를 추가하고 매퍼에서 안전한 getter를 사용하세요.

주의할 위치:

  • src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java — (약칭) 50-53, 59-62: 정렬 필드 선언 및 관련 getter 추가 필요

제안 코드(diff):

   public int getLimit() {
       return size;
   }
+
+  // 안전한 정렬 파라미터 반환
+  public String getSafeSortBy() {
+      java.util.List<String> allowed = java.util.Arrays.asList(
+          "createdAt", "depositPrice", "monthlyRent", "viewCnt", "likeCnt"
+      );
+      if (sortBy == null) return "createdAt";
+      String normalized = sortBy.trim();
+      return allowed.contains(normalized) ? normalized : "createdAt";
+  }
+
+  public String getSafeSortDirection() {
+      if (sortDirection == null) return "DESC";
+      String dir = sortDirection.trim().toUpperCase();
+      return ("ASC".equals(dir) || "DESC".equals(dir)) ? dir : "DESC";
+  }
 }

매퍼 사용 예: ORDER BY ${safeSortBy} ${safeSortDirection} — 사용자 입력을 직접 ${}로 넣지 마세요.

검증 요청: 제공하신 스크립트 실행 결과 출력이 없었으니, 로컬 리포지토리에서 아래 명령어로 위험한 동적 치환(${...}) 사용 여부를 확인해 결과를 알려주세요.

  • rg -n '${.(sort|sortBy|sortDirection).?}' -S

결과를 보내주시면 코멘트를 확정하거나 추가 수정 제안을 드리겠습니다.

src/main/java/org/scoula/domain/chat/controller/ContractChatController.java (2)

146-173: 최종 특약 수정 요청/조회 API 추가 — 일관된 ApiResponse 및 Swagger 적용 확인

신규 수정 요청/응답/조회/대기확인 엔드포인트들이 모두 ResponseEntity<ApiResponse<...>>와 Swagger 메타를 일관적으로 사용하고 있습니다. 설계상 적절해 보입니다.


189-208: 최종 특약 삭제 요청/수락/거절 API 신설 — 경로/래퍼/어노테이션 일관성 양호

경로 체계와 응답 래핑, Swagger 메타가 잘 정리되어 있습니다. 서비스/권한체크 연동만 재확인해 주세요.

src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java (1)

139-162: 메시지 조회 엔드포인트 접근 제어 로직 적절함

인증 사용자 검증 및 채팅방 참여자 확인 후 메시지를 반환하는 접근 제어 흐름이 명확하고 일관적입니다.

src/main/java/org/scoula/domain/chat/service/ContractChatServiceInterface.java (1)

256-261: AI 메시지 메서드 유지 확인

컨트롤러에서 사용하는 AiMessage가 인터페이스에 선언되어 있어 일관성 유지가 확인되었습니다.

src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java (2)

94-104: 엔드포인트 응답 래퍼 일관성 준수 확인

모든 엔드포인트가 ApiResponse<T> 또는 프로젝트 표준 PageResponse로 래핑되어 반환됩니다. 일관성 측면에서 양호합니다.

Also applies to: 121-139, 216-231, 248-276, 278-299


50-87: 확인 완료 — HomeControllerImpl이 사용하는 DTO 타입/명칭 일치

HomeControllerImpl는 org.scoula.domain.home.dto.HomeCreateRequestDto(루트)를 import/사용하고 있으며, 해당 루트 DTO의 필드 타입은 HomeCreateDTO와 일치합니다. dto/request 패키지에 다른 HomeCreateRequestDto가 존재하지만 컨트롤러에서는 사용되지 않습니다.

  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
    • import: org.scoula.domain.home.dto.HomeCreateRequestDto
    • 매핑에서 사용: requestDto.getHomeFloor(), getBathroomCnt(), getFacilityItemIds() 등
  • src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java (루트)
    • homeFloor: Integer
    • bathroomCnt: Integer
    • facilityItemIds: List
  • src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java (타겟 DTO)
    • homeFloor: Integer
    • bathroomCnt: Integer
    • facilityItemIds: List<Integer)
  • 참고 — src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (대체 DTO, 컨트롤러 미사용)
    • homeFloor: String
    • bathroomCount: Integer
    • facilityItemIds: List<Long)

결론: 해당 컨트롤러 내 매핑은 문제없으므로 변경 불필요합니다.

src/main/java/org/scoula/domain/home/controller/HomeController.java (1)

20-23: Swagger2 어노테이션 사용 양호

Swagger 어노테이션이 적절히 부여되어 API 문서화 일관성이 확보되어 있습니다.

Also applies to: 24-26

src/main/java/org/scoula/domain/chat/service/ContractChatServiceImpl.java (2)

1187-1187: 라운드 응답 순서 보장을 위한 LinkedHashMap 적용 좋습니다

응답의 일관된 정렬을 보장하여 클라이언트 처리 용이성이 높아집니다.


1577-1702: 최종 특약 저장 로직의 라운드 백트래킹 처리 적절

최신 라운드에서 빈 조항은 이전 라운드로 백트래킹하여 채우는 전략이 누락 없이 최종본을 구성하는 데 유효합니다. 정렬 및 상세 로그도 유지보수에 도움이 됩니다.

src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1)

10-55: MyBatis 인터페이스 구성 적절

@Mapper 사용, 메서드별 @param 지정, VO/DTO 기반 시그니처가 명확합니다. XML과의 매핑 네이밍도 일관적입니다.

config-submodule Outdated
@@ -1 +1 @@
Subproject commit 821c2c2ab5a06ba5e9f3e7ed6f23041d5c36bbec
Subproject commit 04f53b7d1a2b9d22c3abaa07759c5a98fe287200
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

서브모듈 포인터 업데이트: 커밋 접근성·CI 서브모듈 단계 확인 요청

config-submodule이 커밋 04f53b7d…로 고정되었습니다. 서브모듈 특성상 다음을 확인해 주세요:

  • 해당 커밋이 원격 저장소의 브랜치/태그에서 도달 가능(reachable)한지(특히 shallow fetch 환경에서 중요).
  • .gitmodules 내 경로/URL 불일치 여부, 그리고 CI 파이프라인에 git submodule sync --recursive && git submodule update --init --recursive 단계가 포함되어 있는지.
  • 이 서브모듈 버전이 상위 레포 변경(홈/채팅 DTO/VO 리뉴얼, LocalDate 포맷터 등)과 호환되는지.

아래 스크립트로 기본 점검을 자동화할 수 있습니다.

추가로 운영 측면 제안:

  • 원시 커밋 해시 대신 태그(예: vX.Y.Z)에 맞춰 고정하면 추적성과 커뮤니케이션이 개선됩니다.
  • PR 본문에 서브모듈 변경 이유와 참조 커밋/태그 링크를 덧붙이면 리뷰어들이 영향도를 빠르게 파악할 수 있습니다.

🏁 Script executed:

#!/bin/bash
set -euo pipefail

SUB_PATH="config-submodule"

# 1) .gitmodules에서 서브모듈 식별
if ! test -f .gitmodules; then
  echo "[ERR] .gitmodules 파일이 없습니다."; exit 1
fi

name=$(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' \
  | awk -v p="$SUB_PATH" '$2==p { match($1,/^submodule\.(.*)\.path$/,m); print m[1] }')

if [ -z "${name:-}" ]; then
  echo "[ERR] .gitmodules에 '${SUB_PATH}' 경로의 서브모듈이 등록되어 있지 않습니다."; exit 1
fi

url=$(git config -f .gitmodules --get "submodule.${name}.url" || true)
if [ -z "${url:-}" ]; then
  echo "[ERR] 서브모듈 '${name}'의 URL을 찾을 수 없습니다."; exit 1
fi

echo "[INFO] submodule name: ${name}"
echo "[INFO] submodule url : ${url}"
echo "[INFO] submodule path: ${SUB_PATH}"

# 2) 현재 HEAD에 고정된 커밋 확인
pinned=$(git ls-tree HEAD -- "${SUB_PATH}" | awk '{print $3}')
if [ -z "${pinned:-}" ]; then
  echo "[ERR] '${SUB_PATH}'의 고정 커밋을 확인할 수 없습니다."; exit 1
fi
echo "[INFO] pinned commit : ${pinned}"

# 3) 원격에 해당 커밋이 광고(ref)로 노출되는지 확인(미노출 시 shallow 환경에서 실패 가능)
if git ls-remote "${url}" | awk '{print $1}' | grep -qi "${pinned}"; then
  echo "[OK] 원격 refs에서 해당 커밋을 찾았습니다(브랜치/태그를 통해 도달 가능)."
else
  echo "[WARN] 원격 refs에서 해당 커밋이 보이지 않습니다."
  echo "      -> shallow fetch 환경에서 'git submodule update'가 실패할 수 있으니,"
  echo "         브랜치/태그에 포함되었는지 확인하거나 CI에서 얕은 클론을 해제하세요."
fi

# 4) CI 권장 단계 출력
cat <<'EOM'
[RECOMMENDATION]
- CI에 아래 단계를 포함하세요:
  git submodule sync --recursive
  git submodule update --init --recursive

- 얕은 클론(shallow) 사용 시, 서브모듈 fetch가 실패하면:
  git submodule update --init --recursive --depth 1
  가 실패할 수 있으니 depth 옵션을 제거하거나
  원격 브랜치/태그에서 커밋이 도달 가능하도록 관리하세요.
EOM

Length of output: 1918


조치 필요 — config-submodule 원격 접근 불가 (04f53b7d…)

검증 요약: .gitmodules에서 submodule 'config-submodule' 확인됨 (url: https://github.com/ITZEEP/backend-config.git). HEAD에 고정된 커밋 04f53b7d1a2b9d22c3abaa07759c5a98fe287200 확인됨. 그러나 git ls-remote 실행 결과 "Repository not found" 로 원격 refs에서 커밋(또는 저장소)을 찾을 수 없습니다 — CI에서 서브모듈 fetch/update 실패 가능성이 있습니다.

필요한 확인 및 조치:

  • .gitmodules (.gitmodules) — submodule name: config-submodule, url: https://github.com/ITZEEP/backend-config.git (확인됨).
  • 고정 커밋: 04f53b7d1a2b9d22c3abaa07759c5a98fe287200 (HEAD).
  • 문제 원인 점검:
    • 원격 저장소 존재 여부 및 URL 정확성 확인: git ls-remote https://github.com/ITZEEP/backend-config.git
    • 저장소가 private인 경우 CI에서 접근 가능한 자격증명(SSH key 또는 토큰) 설정 필요.
    • 저장소가 이동/삭제되었는지 확인하고 .gitmodules의 URL을 갱신.
  • CI 설정 권장사항:
    • CI 파이프라인에 반드시 포함: git submodule sync --recursive && git submodule update --init --recursive
    • 얕은 클론 사용 시 depth 제거 또는 원격 브랜치/태그에서 커밋이 도달 가능하도록 관리(또는 depth 없이 fetch).
  • 운영권장:
    • 가능한 경우 원시 커밋 해시 대신 태그(vX.Y.Z)로 고정(추적성 향상).
    • PR 본문에 서브모듈 변경 이유와 원격 커밋/태그 링크 추가.
  • 호환성 검증: 해당 서브모듈 커밋이 상위 레포 변경(예: 홈/채팅 DTO/VO 리뉴얼, LocalDate 포맷터 등)과 호환되는지 수동으로 확인 필요.

요약: 원격 접근 불가 문제로 인해 조치가 필요합니다 — 원격 URL/권한을 확인하거나 .gitmodules/CI 구성을 수정해 주세요.

🤖 Prompt for AI Agents
In config-submodule around lines 1 to 1, the submodule entry points to
https://github.com/ITZEEP/backend-config.git pinned at commit 04f53b7d… which is
not reachable via git ls-remote; verify the remote repository exists and the URL
is correct, update .gitmodules to a reachable URL if changed, or ensure the repo
is accessible to CI by adding appropriate credentials (SSH key or token) and
syncing submodules; update CI to run git submodule sync --recursive && git
submodule update --init --recursive (avoid shallow depth that prevents fetching
the pinned commit), consider pinning to a tag instead of a raw commit, and
document the submodule change and remote commit/tag link in the PR.

Comment on lines 695 to 704
public ResponseEntity<ApiResponse<ModificationRequestData>> requestFinalContractModification(
@PathVariable Long contractChatId,
@RequestBody FinalContractModificationRequestDto requestDto,
Authentication authentication) {
try {
Long userId = getUserIdFromAuthentication(authentication);

requestDto.setContractChatId(contractChatId);

ModificationRequestData result =
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

요청 DTO 검증 추가: @Valid 누락

수정 요청/응답 DTO에 대한 유효성 검증이 없습니다. 필수 필드 누락 시 서비스 레이어까지 흘러들어갈 수 있습니다. @Valid를 추가하세요.

-      public ResponseEntity<ApiResponse<ModificationRequestData>> requestFinalContractModification(
+      public ResponseEntity<ApiResponse<ModificationRequestData>> requestFinalContractModification(
               @PathVariable Long contractChatId,
-              @RequestBody FinalContractModificationRequestDto requestDto,
+              @Valid @RequestBody FinalContractModificationRequestDto requestDto,
               Authentication authentication) {
-      public ResponseEntity<ApiResponse<FinalSpecialContractDocument>> respondToModificationRequest(
+      public ResponseEntity<ApiResponse<FinalSpecialContractDocument>> respondToModificationRequest(
               @PathVariable Long contractChatId,
-              @RequestBody FinalContractModificationResponseDto responseDto,
+              @Valid @RequestBody FinalContractModificationResponseDto responseDto,
               Authentication authentication) {

Also applies to: 720-729

🤖 Prompt for AI Agents
In
src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java
around lines 695-704 (and similarly at 720-729), the controller methods accept
request DTOs without JSR-380 validation; add the @Valid annotation to the
@RequestBody parameters for FinalContractModificationRequestDto (and the other
modification DTO at 720-729), import the appropriate javax.validation.Valid or
jakarta.validation.Valid, and ensure method signatures validate incoming DTOs
before reaching the service layer so missing required fields are rejected at the
controller level.

Comment on lines 885 to 901
@Override
public ResponseEntity<ApiResponse<String>> requestFinalContractDeletion(
Long contractChatId, Integer clauseOrder, Authentication authentication) {
try {
Long userId = getUserIdFromAuthentication(authentication);
contractChatService.requestFinalContractDeletion(contractChatId, userId, clauseOrder);

return ResponseEntity.ok(
ApiResponse.success(
String.format("특약 %d번 삭제 요청이 임차인에게 전송되었습니다.", clauseOrder)));
} catch (Exception e) {
log.error("최종 특약 삭제 요청 실패", e);
return ResponseEntity.badRequest()
.body(ApiResponse.error("최종 특약 삭제 요청에 실패했습니다: " + e.getMessage()));
}
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

최종 특약 삭제 플로우 엔드포인트에 @PostMapping 누락

requestFinalContractDeletion, acceptFinalContractDeletion, rejectFinalContractDeletion 메서드에 HTTP 매핑 애너테이션이 없습니다. 현재 상태로는 라우팅되지 않습니다. 아래와 같이 매핑과 @RequestParam을 추가해 주세요.

       @Override
-      public ResponseEntity<ApiResponse<String>> requestFinalContractDeletion(
-              Long contractChatId, Integer clauseOrder, Authentication authentication) {
+      @PostMapping("/{contractChatId}/final-contract/deletion")
+      public ResponseEntity<ApiResponse<String>> requestFinalContractDeletion(
+              @PathVariable Long contractChatId,
+              @RequestParam Integer clauseOrder,
+              Authentication authentication) {
           try {
               ...
           }
       }
       @Override
-      public ResponseEntity<ApiResponse<Map<String, Object>>> acceptFinalContractDeletion(
-              Long contractChatId, Integer clauseOrder, Authentication authentication) {
+      @PostMapping("/{contractChatId}/final-contract/accept-deletion")
+      public ResponseEntity<ApiResponse<Map<String, Object>>> acceptFinalContractDeletion(
+              @PathVariable Long contractChatId,
+              @RequestParam Integer clauseOrder,
+              Authentication authentication) {
           try {
               ...
           }
       }
       @Override
-      public ResponseEntity<ApiResponse<String>> rejectFinalContractDeletion(
-              Long contractChatId, Integer clauseOrder, Authentication authentication) {
+      @PostMapping("/{contractChatId}/final-contract/reject-deletion")
+      public ResponseEntity<ApiResponse<String>> rejectFinalContractDeletion(
+              @PathVariable Long contractChatId,
+              @RequestParam Integer clauseOrder,
+              Authentication authentication) {
           try {
               ...
           }
       }

Also applies to: 902-934, 935-965

🤖 Prompt for AI Agents
In
src/main/java/org/scoula/domain/chat/controller/ContractChatControllerImpl.java
around lines 885-901 (and similarly for blocks at 902-934 and 935-965), the
three methods for final contract deletion lack HTTP mapping annotations and
request parameter binding; add @PostMapping on each method with an appropriate
endpoint path (e.g., /contracts/{contractChatId}/final-deletion/request,
/accept, /reject or a project-standard path), annotate the contractChatId and
clauseOrder parameters with @PathVariable/@RequestParam as appropriate (or both
as @RequestParam if you prefer query params), and ensure the methods accept the
Authentication parameter unchanged; this will expose the endpoints to Spring MVC
routing and correctly bind incoming HTTP values.

Comment on lines 5 to 15
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FinalContractModificationRequestDto {
private Long contractChatId;
private Integer clauseOrder;
private String newTitle;
private String newContent;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

최소 입력 검증 추가 권장(@NotNull/@notblank)

계약 수정 요청의 핵심 필드들에 대한 필수/공백 검증이 없습니다. 컨트롤러에서 @Valid를 사용한다면 런타임에서 즉시 검증 가능하도록 보강하세요.

 package org.scoula.domain.chat.dto;

 import lombok.*;
+import javax.validation.constraints.*;

 @Getter
 @Setter
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class FinalContractModificationRequestDto {
-      private Long contractChatId;
-      private Integer clauseOrder;
-      private String newTitle;
-      private String newContent;
+      @NotNull
+      private Long contractChatId;
+      @NotNull
+      private Integer clauseOrder;
+      @NotBlank
+      private String newTitle;
+      @NotBlank
+      private String newContent;
 }
📝 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
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FinalContractModificationRequestDto {
private Long contractChatId;
private Integer clauseOrder;
private String newTitle;
private String newContent;
}
package org.scoula.domain.chat.dto;
import lombok.*;
import javax.validation.constraints.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FinalContractModificationRequestDto {
@NotNull
private Long contractChatId;
@NotNull
private Integer clauseOrder;
@NotBlank
private String newTitle;
@NotBlank
private String newContent;
}
🤖 Prompt for AI Agents
In
src/main/java/org/scoula/domain/chat/dto/FinalContractModificationRequestDto.java
around lines 5 to 15, the DTO lacks bean validation on critical fields; add
javax.validation annotations: annotate contractChatId and clauseOrder with
@NotNull, and newTitle and newContent with @NotBlank (or @NotEmpty if empty
string allowed) and import the corresponding javax.validation.constraints.*
types; keep Lombok annotations, regenerate/compile, and ensure controllers
accept the DTO with @Valid so these constraints are enforced at runtime.

Comment on lines +441 to 444
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeLikes(Integer userId) {
return List.of();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

사용자 찜 목록 조회 미구현

현재 빈 리스트를 반환합니다. Mapper를 호출하여 목록을 구성하세요.

       public List<HomeResponseDTO> getHomeLikes(Integer userId) {
-          return List.of();
+          List<HomeVO> homes = homeMapper.selectHomeLikesByUserId(userId);
+          return homes.stream()
+                  .map(home -> {
+                      List<HomeImageVO> images = homeMapper.selectHomeImagesByHomeId(home.getHomeId());
+                      String mainImageUrl = images.isEmpty() ? null : images.get(0).getImageUrl();
+                      return HomeResponseDTO.builder()
+                              .homeId(home.getHomeId())
+                              .addr1(home.getAddr1())
+                              .residenceType(home.getResidenceType())
+                              .leaseType(home.getLeaseType())
+                              .depositPrice(home.getDepositPrice())
+                              .monthlyRent(home.getMonthlyRent())
+                              .maintenaceFee(home.getMaintenaceFee())
+                              .homeStatus(home.getHomeStatus())
+                              .exclusiveArea(home.getExclusiveArea())
+                              .homeFloor(home.getHomeFloor())
+                              .viewCnt(home.getViewCnt())
+                              .likeCnt(home.getLikeCnt())
+                              .roomCnt(home.getRoomCnt())
+                              .supplyArea(home.getSupplyArea())
+                              .imageUrls(mainImageUrl != null ? List.of(mainImageUrl) : List.of())
+                              .createdAt(home.getCreatedAt())
+                              .build();
+                  })
+                  .collect(Collectors.toList());
       }
📝 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
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeLikes(Integer userId) {
return List.of();
}
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeLikes(Integer userId) {
List<HomeVO> homes = homeMapper.selectHomeLikesByUserId(userId);
return homes.stream()
.map(home -> {
List<HomeImageVO> images = homeMapper.selectHomeImagesByHomeId(home.getHomeId());
String mainImageUrl = images.isEmpty() ? null : images.get(0).getImageUrl();
return HomeResponseDTO.builder()
.homeId(home.getHomeId())
.addr1(home.getAddr1())
.residenceType(home.getResidenceType())
.leaseType(home.getLeaseType())
.depositPrice(home.getDepositPrice())
.monthlyRent(home.getMonthlyRent())
.maintenaceFee(home.getMaintenaceFee())
.homeStatus(home.getHomeStatus())
.exclusiveArea(home.getExclusiveArea())
.homeFloor(home.getHomeFloor())
.viewCnt(home.getViewCnt())
.likeCnt(home.getLikeCnt())
.roomCnt(home.getRoomCnt())
.supplyArea(home.getSupplyArea())
.imageUrls(mainImageUrl != null ? List.of(mainImageUrl) : List.of())
.createdAt(home.getCreatedAt())
.build();
})
.collect(Collectors.toList());
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java around
lines 441 to 444, the getHomeLikes method currently returns an empty list;
replace this with logic that queries the repository for the user's liked homes,
maps the resulting entities to HomeResponseDTO using the existing mapper, and
returns that list (preserve @Transactional(readOnly = true)). Specifically: call
the repository method that fetches likes for the given userId, pass the returned
entity list to the mapper to convert to DTOs, and return the mapped list; handle
nulls by returning an empty list if the repository returns null.

Comment on lines +12 to +15
private Integer homeId;
private Integer maintenanceId;
private Integer fee;
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

금액 필드 설계와 명칭 정합성 확인 필요

  • fee가 화폐 금액이라면 오버플로/정밀도 요구사항에 따라 Integer 대신 Long/BigDecimal 검토가 필요합니다.
  • HomeVO에 maintenaceFee(오탈자) 필드가 보입니다. 본 VO의 fee, DTO/Mapper, DB 컬럼과 명칭 정합성 확인이 필요합니다(오탈자로 인한 매핑 오류 위험).

다음 스크립트로 관련 명칭 사용 현황을 점검해 주세요:

원하시면 금액 타입(BigDecimal) 전환과 명칭 정합성(Mapper/XML 포함) 일괄 리팩터링 패치를 제안드리겠습니다.


🏁 Script executed:

#!/bin/bash
# maintenaceFee(오탈자)와 maintenanceFee/fee 사용처를 비교
rg -n -S $'maintenaceFee|maintenanceFee\\b|\\bfee\\b' --hidden

Length of output: 7395


관리비 필드 오타(maintenaceFee) 및 금액 타입 점검 필요

검증 결과: 코드베이스에 'maintenaceFee'(오탈자)와 'maintenanceFee'가 혼재되어 있으며, DB·매퍼는 현재 fee를 INT/Integer로 사용하고 있습니다. 아래 파일들을 점검/수정해야 합니다.

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
    • #{maintenaceFee}, h.maintenance_fee as maintenaceFee, maintenance_fee = #{maintenaceFee} 등 오타 사용 위치 다수 — 매퍼 alias/파라미터 명칭 정합성 필요
  • src/main/java/org/scoula/domain/home/vo/HomeVO.java
    • private Integer maintenaceFee; → maintenanceFee로 변경 권장
  • src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java
    • private Integer maintenaceFee; → maintenanceFee로 변경
  • src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java
    • private Integer maintenaceFee; → maintenanceFee로 변경
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
    • .maintenaceFee(requestDto.getMaintenanceFee()) 등 호출부 수정 필요
  • src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
    • createDTO.getMaintenaceFee(), home.getMaintenaceFee() 등 사용부 수정 필요
  • 참고: src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java는 private Integer fee;로 정의되어 있고, init/database.sql에는 fee INT로 되어 있어 현재 타입은 Integer/INT로 일치함

권장 조치:

  1. 코드 전체에서 maintenaceFee → maintenanceFee로 통일(VO/DTO/Controller/Service/Mapper alias 및 MyBatis 파라미터 포함).
  2. 금액 필드가 화폐(소수·정밀도/오버플로우)를 다뤄야 한다면 DB(INT) → DECIMAL(예: DECIMAL(13,2)) 및 Java Integer → BigDecimal로 전환(매퍼 타입 설정·API 영향·마이그레이션 검토 포함).
  3. 변경 시 매핑(Mapper/XML)과 직렬화/역직렬화(REST DTO) 영향 범위 테스트 수행.

원하시면 명칭 정정 및 BigDecimal 전환을 포함한 일괄 리팩터링 패치(변경 파일 목록 + Diff) 제안해 드리겠습니다.

private LeaseType leaseType;
private Integer depositPrice;
private Integer monthlyRent;
private Integer maintenaceFee;
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

타이포: maintenaceFee → maintenanceFee (네이밍 표준화 필요)

maintenaceFee는 오타로 보입니다. DTO/요청객체에서는 maintenanceFee를 사용 중이라 계층 간 불일치가 있습니다. 필드명 표준화가 필요합니다.

로컬 변경 diff:

-      private Integer maintenaceFee;
+      private Integer maintenanceFee;

전역 사용처 점검 스크립트:


🏁 Script executed:

#!/bin/bash
# 오타 필드 사용 전역 검색
rg -n 'maintenaceFee' -S

Length of output: 2034


타이포 수정 필요: maintenaceFee → maintenanceFee (전역 일괄 수정)

VO 필드명이 오타입니다. 리포지토리 전역에서 maintenaceFee가 사용되고 있으니 모든 참조를 maintenanceFee로 통일해 주세요.

수정해야 할 위치(스크립트 결과 기준):

  • src/main/java/org/scoula/domain/home/vo/HomeVO.java:26
  • src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java:35
  • src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java:35
  • src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java: lines 50, 214, 258
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java: lines 59, 169
  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml: lines 13, 54, 76, 93, 233, 248, 265 (alias 및 #{...} 파라미터 포함)

권장 변경 예시(diff):

  • 필드
- private Integer maintenaceFee;
+ private Integer maintenanceFee;
  • MyBatis 매퍼(alias / 파라미터)
- h.maintenance_fee as maintenaceFee
+ h.maintenance_fee as maintenanceFee

- #{maintenaceFee}
+ #{maintenanceFee}

- maintenance_fee = #{maintenaceFee},
+ maintenance_fee = #{maintenanceFee},
  • 빌더/메서드 호출
- .maintenaceFee(createDTO.getMaintenaceFee())
+ .maintenanceFee(createDTO.getMaintenanceFee())

권장 작업: IDE의 Rename(Refactor) 기능으로 식별자 일괄 변경(VO/DTO 필드, getter/setter, 빌더 메서드, 매퍼 alias/파라미터) 후 컴파일/테스트로 영향 범위 확인 바랍니다.

📝 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
private Integer maintenaceFee;
private Integer maintenanceFee;
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/home/vo/HomeVO.java around line 26 (and
across the repository in the listed files/lines), the field name maintenaceFee
is misspelled and should be maintenanceFee; perform a project-wide
rename/refactor (VO, DTOs, service, controller, MyBatis mapper aliases and
#{...} parameters, builders, getters/setters and any usages) to change
maintenaceFee → maintenanceFee, update corresponding method names and mapper
parameter names, then rebuild and run tests to ensure all references (including
lines in HomeCreateDTO.java:35, HomeResponseDTO.java:35,
HomeServiceImpl.java:50/214/258, HomeControllerImpl.java:59/169, and
HomeMapper.xml at the listed line numbers) are updated consistently.

Comment on lines +6 to 16
<insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
INSERT INTO home (
user_id, user_name,
addr1, addr2, residence_type, lease_type, deposit_price,
monthly_rent, maintenance_fee, supply_area, exclusive_area, home_status,
view_cnt, like_cnt, chat_cnt, report_cnt,
room_cnt, created_at, updated_at
user_id, addr1, addr2, residence_type, lease_type,
deposit_price, monthly_rent, maintenance_fee, home_status,
view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area
) VALUES (
#{userId}, #{userName},
#{home.addr1}, #{home.addr2}, #{home.residenceType}, #{home.leaseType}, #{home.depositPrice},
#{home.monthlyRent}, #{home.maintenanceFee}, #{home.supplyArea}, #{home.exclusiveArea}, #{home.homeStatus},
0, 0, 0, 0,
#{home.roomCnt}, NOW(), NOW()
#{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
#{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
#{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}
)
</insert>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

insertHome에 created_at/updated_at 미포함 — 감사/정렬 정보 손실 가능

Service에서 createdAt/updatedAt을 세팅하지만, INSERT에 포함되지 않아 DB에 저장되지 않을 수 있습니다. DB default가 없다면 NOW()로 직접 채우거나 파라미터로 전달하세요.

     <insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
         INSERT INTO home (
-            user_id, addr1, addr2, residence_type, lease_type,
-            deposit_price, monthly_rent, maintenance_fee, home_status,
-            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area
+            user_id, addr1, addr2, residence_type, lease_type,
+            deposit_price, monthly_rent, maintenance_fee, home_status,
+            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area,
+            created_at, updated_at
         ) VALUES (
-                     #{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
-                     #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
-                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}
+                     #{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
+                     #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
+                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea},
+                     NOW(), NOW()
         )
     </insert>
📝 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
<insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
INSERT INTO home (
user_id, user_name,
addr1, addr2, residence_type, lease_type, deposit_price,
monthly_rent, maintenance_fee, supply_area, exclusive_area, home_status,
view_cnt, like_cnt, chat_cnt, report_cnt,
room_cnt, created_at, updated_at
user_id, addr1, addr2, residence_type, lease_type,
deposit_price, monthly_rent, maintenance_fee, home_status,
view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area
) VALUES (
#{userId}, #{userName},
#{home.addr1}, #{home.addr2}, #{home.residenceType}, #{home.leaseType}, #{home.depositPrice},
#{home.monthlyRent}, #{home.maintenanceFee}, #{home.supplyArea}, #{home.exclusiveArea}, #{home.homeStatus},
0, 0, 0, 0,
#{home.roomCnt}, NOW(), NOW()
#{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
#{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
#{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}
)
</insert>
<insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
INSERT INTO home (
user_id, addr1, addr2, residence_type, lease_type,
deposit_price, monthly_rent, maintenance_fee, home_status,
view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area, created_at, updated_at
) VALUES (
#{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
#{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
#{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}, NOW(), NOW()
)
</insert>
🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines
6 to 16, the INSERT mapping for insertHome omits created_at and updated_at so
timestamps set in service may not be persisted; update the INSERT to include
created_at and updated_at columns and provide corresponding values (either bind
the HomeVO params like #{createdAt}, #{updatedAt} or use DB defaults e.g. NOW()
if acceptable), ensuring parameter names match the HomeVO fields and keep
useGeneratedKeys/keyProperty unaffected.


@Override
@GetMapping("/{homeId}")
public ResponseEntity<ApiResponse<HomeResponseDTO>> getHome(@PathVariable Integer homeId) {

Check failure

Code scanning / CodeQL

HTTP request type unprotected from CSRF High

Potential CSRF vulnerability due to using an HTTP request type which is not default-protected from CSRF for an apparent
state-changing action
.

Copilot Autofix

AI 7 months ago

To fix the problem, we need to ensure that the GET endpoint does not perform any state-changing operations. The best way to do this is to separate the view count increment from the data retrieval. Specifically:

  • The @GetMapping("/{homeId}") endpoint should only retrieve and return the home details, without incrementing the view count.
  • If incrementing the view count is still required, it should be exposed via a separate endpoint that uses an unsafe HTTP method (e.g., POST or PATCH), which is protected by default from CSRF in Spring.
  • In HomeServiceImpl.getHome, remove the call to homeMapper.incrementViewCount(homeId).
  • Optionally, add a new endpoint in HomeControllerImpl (e.g., @PostMapping("/{homeId}/view")) that calls a new service method to increment the view count.

This approach preserves existing functionality while ensuring that state changes are not performed via safe HTTP methods.


Suggested changeset 2
src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
--- a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
+++ b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
@@ -102,6 +102,15 @@
           return ResponseEntity.ok(ApiResponse.success(home, "매물 조회가 완료되었습니다."));
       }
 
+      /**
+       * Increments the view count for a home.
+       */
+      @PostMapping("/{homeId}/view")
+      public ResponseEntity<ApiResponse<Void>> incrementHomeViewCount(@PathVariable Integer homeId) {
+          homeService.incrementHomeViewCount(homeId);
+          return ResponseEntity.ok(ApiResponse.success(null, "View count incremented."));
+      }
+
       @Override
       @GetMapping
       public ResponseEntity<PageResponse<HomeResponseDTO>> getHomeList(
EOF
@@ -102,6 +102,15 @@
return ResponseEntity.ok(ApiResponse.success(home, "매물 조회가 완료되었습니다."));
}

/**
* Increments the view count for a home.
*/
@PostMapping("/{homeId}/view")
public ResponseEntity<ApiResponse<Void>> incrementHomeViewCount(@PathVariable Integer homeId) {
homeService.incrementHomeViewCount(homeId);
return ResponseEntity.ok(ApiResponse.success(null, "View count incremented."));
}

@Override
@GetMapping
public ResponseEntity<PageResponse<HomeResponseDTO>> getHomeList(
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -179,7 +179,7 @@
       @Override
       @Transactional(readOnly = true)
       public HomeResponseDTO getHome(Integer homeId) {
-          homeMapper.incrementViewCount(homeId);
+          // Removed incrementViewCount to avoid state change on GET
 
           HomeVO home = homeMapper.selectHomeById(homeId);
           if (home == null) {
@@ -235,7 +235,16 @@
                   .build();
       }
 
+      /**
+       * Increments the view count for a home.
+       */
       @Override
+      @Transactional
+      public void incrementHomeViewCount(Integer homeId) {
+          homeMapper.incrementViewCount(homeId);
+      }
+
+      @Override
       @Transactional(readOnly = true)
       public List<HomeResponseDTO> searchHomes(HomeSearchDTO searchDTO) {
           List<HomeVO> homes = homeMapper.selectHomeListByCondition(searchDTO);
EOF
@@ -179,7 +179,7 @@
@Override
@Transactional(readOnly = true)
public HomeResponseDTO getHome(Integer homeId) {
homeMapper.incrementViewCount(homeId);
// Removed incrementViewCount to avoid state change on GET

HomeVO home = homeMapper.selectHomeById(homeId);
if (home == null) {
@@ -235,7 +235,16 @@
.build();
}

/**
* Increments the view count for a home.
*/
@Override
@Transactional
public void incrementHomeViewCount(Integer homeId) {
homeMapper.incrementViewCount(homeId);
}

@Override
@Transactional(readOnly = true)
public List<HomeResponseDTO> searchHomes(HomeSearchDTO searchDTO) {
List<HomeVO> homes = homeMapper.selectHomeListByCondition(searchDTO);
Copilot is powered by AI and may make mistakes. Always verify output.

Integer userId = getCurrentUserId(authentication);

log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, status, userId);

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the log injection vulnerability, sanitize the status parameter before logging it. The best approach is to remove or escape newline and carriage return characters from status, ensuring that user input cannot break log lines or inject new entries. This can be done by replacing \r and \n with empty strings before logging. The change should be made in the updateHomeStatus method, specifically before the log statement. No new methods are needed, but a simple sanitization line should be added. No new imports are required, as String.replaceAll is part of the Java standard library.


Suggested changeset 1
src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
--- a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
+++ b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
@@ -223,7 +223,9 @@
 
           Integer userId = getCurrentUserId(authentication);
 
-          log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, status, userId);
+          // Sanitize status to prevent log injection
+          String sanitizedStatus = status.replaceAll("[\\r\\n]", "");
+          log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, sanitizedStatus, userId);
 
           homeService.updateHomeStatus(homeId, status, userId);
 
EOF
@@ -223,7 +223,9 @@

Integer userId = getCurrentUserId(authentication);

log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, status, userId);
// Sanitize status to prevent log injection
String sanitizedStatus = status.replaceAll("[\\r\\n]", "");
log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, sanitizedStatus, userId);

homeService.updateHomeStatus(homeId, status, userId);

Copilot is powered by AI and may make mistakes. Always verify output.
log.info(
"이미지 업로드 완료: homeId={}, fileName={}, imageUrl={}",
homeId,
fileName,

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the log injection vulnerability, we should sanitize the user-controlled value before logging it. Specifically, in the generateHomeImageFileName method, the extension extracted from originalFileName should be sanitized to remove any newline or carriage return characters (and possibly other control characters). This can be done using String.replaceAll("[\\r\\n]", "") or similar. The sanitized extension should then be used in the filename construction. This change should be made in the generateHomeImageFileName method in src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java. No new methods or imports are required, as this can be done with standard Java string methods.


Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -171,6 +171,8 @@
 
           if (originalFileName != null && originalFileName.contains(".")) {
               extension = originalFileName.substring(originalFileName.lastIndexOf("."));
+              // Remove any newline or carriage return characters to prevent log injection
+              extension = extension.replaceAll("[\\r\\n]", "");
           }
 
           return homeId + "_" + timestamp + extension;
EOF
@@ -171,6 +171,8 @@

if (originalFileName != null && originalFileName.contains(".")) {
extension = originalFileName.substring(originalFileName.lastIndexOf("."));
// Remove any newline or carriage return characters to prevent log injection
extension = extension.replaceAll("[\\r\\n]", "");
}

return homeId + "_" + timestamp + extension;
Copilot is powered by AI and may make mistakes. Always verify output.
log.error(
"이미지 업로드 실패: homeId={}, fileName={}",
homeId,
image.getOriginalFilename(),

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix this issue, we should sanitize the user-provided filename before logging it. The most straightforward and effective way is to remove or replace newline (\n), carriage return (\r), and other control characters from the filename before passing it to the logger. This can be done by using String.replaceAll() to replace these characters with an underscore or an empty string. The change should be made directly in the logging statement on line 148, or by assigning the sanitized filename to a variable before logging. No new methods or imports are required, as this can be accomplished with standard Java string methods.


Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -145,7 +145,7 @@
                               log.error(
                                       "이미지 업로드 실패: homeId={}, fileName={}",
                                       homeId,
-                                      image.getOriginalFilename(),
+                                      image.getOriginalFilename() != null ? image.getOriginalFilename().replaceAll("[\\r\\n]", "_") : null,
                                       e);
                               // 이미지 업로드 실패는 전체 등록을 중단하지 않음
                           }
EOF
@@ -145,7 +145,7 @@
log.error(
"이미지 업로드 실패: homeId={}, fileName={}",
homeId,
image.getOriginalFilename(),
image.getOriginalFilename() != null ? image.getOriginalFilename().replaceAll("[\\r\\n]", "_") : null,
e);
// 이미지 업로드 실패는 전체 등록을 중단하지 않음
}
Copilot is powered by AI and may make mistakes. Always verify output.
@Override
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeList(int page, int size) {
int offset = (page - 1) * size;

Check failure

Code scanning / CodeQL

User-controlled data in arithmetic expression High

This arithmetic expression depends on a
user-provided value
, potentially causing an underflow.
This arithmetic expression depends on a
user-provided value
, potentially causing an overflow.

Copilot Autofix

AI 7 months ago

To fix this issue, we need to validate the user-supplied page and size parameters before using them in arithmetic expressions. The best approach is to ensure that both page and size are within reasonable, non-negative, and non-zero bounds, and that their product cannot overflow an int. This can be done by:

  • Checking that page >= 1 and size >= 1 (since pagination with page 0 or size 0 makes no sense).
  • Ensuring that (page - 1) * size does not exceed Integer.MAX_VALUE or become negative due to overflow.
  • If the values are invalid, throw a BusinessException with an appropriate error code.

The validation should be performed at the start of the getHomeList method in HomeServiceImpl.java, before calculating the offset. If the values are invalid, throw an exception; otherwise, proceed as normal.


Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -276,6 +276,14 @@
       @Override
       @Transactional(readOnly = true)
       public List<HomeResponseDTO> getHomeList(int page, int size) {
+          // Validate page and size to prevent underflow/overflow
+          if (page < 1 || size < 1) {
+              throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "page and size must be >= 1");
+          }
+          // Prevent integer overflow in offset calculation
+          if ((long)(page - 1) * (long)size > Integer.MAX_VALUE) {
+              throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "Requested page and size are too large");
+          }
           int offset = (page - 1) * size;
           List<HomeVO> homes = homeMapper.selectHomeList(offset, size);
 
EOF
@@ -276,6 +276,14 @@
@Override
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeList(int page, int size) {
// Validate page and size to prevent underflow/overflow
if (page < 1 || size < 1) {
throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "page and size must be >= 1");
}
// Prevent integer overflow in offset calculation
if ((long)(page - 1) * (long)size > Integer.MAX_VALUE) {
throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "Requested page and size are too large");
}
int offset = (page - 1) * size;
List<HomeVO> homes = homeMapper.selectHomeList(offset, size);

Copilot is powered by AI and may make mistakes. Always verify output.
@Override
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeList(int page, int size) {
int offset = (page - 1) * size;

Check failure

Code scanning / CodeQL

User-controlled data in arithmetic expression High

This arithmetic expression depends on a
user-provided value
, potentially causing an underflow.

Copilot Autofix

AI 7 months ago

To fix the problem, we should validate the page and size parameters before performing arithmetic operations. Specifically, we should ensure that page is at least 1 and size is positive and within a reasonable upper bound. If the values are invalid, we should either throw a business exception or set them to safe default values. The validation should be performed at the start of the getHomeList method in HomeServiceImpl.java. Additionally, we should guard the arithmetic operation to prevent integer overflow by checking that (page - 1) and size do not result in a value greater than Integer.MAX_VALUE. If the check fails, we should throw an exception or cap the offset at Integer.MAX_VALUE.

Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -276,6 +276,17 @@
       @Override
       @Transactional(readOnly = true)
       public List<HomeResponseDTO> getHomeList(int page, int size) {
+          // Validate page and size
+          if (page < 1) {
+              throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "page must be >= 1");
+          }
+          if (size < 1 || size > 1000) { // Arbitrary upper bound for size
+              throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "size must be between 1 and 1000");
+          }
+          // Guard against integer overflow
+          if ((long)(page - 1) * (long)size > Integer.MAX_VALUE) {
+              throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "Requested page/size too large");
+          }
           int offset = (page - 1) * size;
           List<HomeVO> homes = homeMapper.selectHomeList(offset, size);
 
EOF
@@ -276,6 +276,17 @@
@Override
@Transactional(readOnly = true)
public List<HomeResponseDTO> getHomeList(int page, int size) {
// Validate page and size
if (page < 1) {
throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "page must be >= 1");
}
if (size < 1 || size > 1000) { // Arbitrary upper bound for size
throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "size must be between 1 and 1000");
}
// Guard against integer overflow
if ((long)(page - 1) * (long)size > Integer.MAX_VALUE) {
throw new BusinessException(CommonErrorCode.INVALID_PARAMETER, "Requested page/size too large");
}
int offset = (page - 1) * size;
List<HomeVO> homes = homeMapper.selectHomeList(offset, size);

Copilot is powered by AI and may make mistakes. Always verify output.
log.info(
"이미지 추가 업로드 완료: homeId={}, fileName={}, imageUrl={}",
homeId,
fileName,

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix this issue, we should sanitize the user-provided filename before logging it. The best approach is to remove any newline (\n, \r) and other potentially dangerous characters from the filename before it is used in the log entry. This can be done by creating a helper method to sanitize the filename, replacing newline and carriage return characters with an empty string. The sanitization should be applied right before logging, ensuring that the logged value cannot break log boundaries or inject new log entries. The changes should be made in src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java, specifically in the block where fileName is logged (lines 347-351). A helper method for sanitization should be added to the class.

Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -176,6 +176,17 @@
           return homeId + "_" + timestamp + extension;
       }
 
+      /**
+       * Sanitizes a string for safe logging by removing newlines and carriage returns.
+       */
+      private String sanitizeForLog(String input) {
+          if (input == null) {
+              return null;
+          }
+          // Remove newlines and carriage returns
+          return input.replaceAll("[\\r\\n]", "");
+      }
+
       @Override
       @Transactional(readOnly = true)
       public HomeResponseDTO getHome(Integer homeId) {
@@ -347,7 +358,7 @@
                           log.info(
                                   "이미지 추가 업로드 완료: homeId={}, fileName={}, imageUrl={}",
                                   homeId,
-                                  fileName,
+                                  sanitizeForLog(fileName),
                                   imageUrl);
                       } catch (Exception e) {
                           log.error(
EOF
@@ -176,6 +176,17 @@
return homeId + "_" + timestamp + extension;
}

/**
* Sanitizes a string for safe logging by removing newlines and carriage returns.
*/
private String sanitizeForLog(String input) {
if (input == null) {
return null;
}
// Remove newlines and carriage returns
return input.replaceAll("[\\r\\n]", "");
}

@Override
@Transactional(readOnly = true)
public HomeResponseDTO getHome(Integer homeId) {
@@ -347,7 +358,7 @@
log.info(
"이미지 추가 업로드 완료: homeId={}, fileName={}, imageUrl={}",
homeId,
fileName,
sanitizeForLog(fileName),
imageUrl);
} catch (Exception e) {
log.error(
Copilot is powered by AI and may make mistakes. Always verify output.
log.error(
"이미지 업로드 실패: homeId={}, fileName={}",
homeId,
image.getOriginalFilename(),

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix this problem, we should sanitize the user-provided filename before logging it. The simplest and most effective way is to remove any newline (\n, \r) characters from the filename, replacing them with a safe character (such as a space or an underscore). This prevents a malicious user from injecting new log entries or manipulating the log format. The sanitization should be performed immediately before logging, and only for the value being logged.

Specifically, in updateHome, on line 356, replace image.getOriginalFilename() with a sanitized version. We can define a helper method in the class, e.g., sanitizeForLog(String input), which removes newlines and carriage returns. Add this method to the class, and use it in the log statement.

No new dependencies are required; this can be done with standard Java string methods.


Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -353,7 +353,7 @@
                           log.error(
                                   "이미지 업로드 실패: homeId={}, fileName={}",
                                   homeId,
-                                  image.getOriginalFilename(),
+                                  sanitizeForLog(image.getOriginalFilename()),
                                   e);
                       }
                   }
EOF
@@ -353,7 +353,7 @@
log.error(
"이미지 업로드 실패: homeId={}, fileName={}",
homeId,
image.getOriginalFilename(),
sanitizeForLog(image.getOriginalFilename()),
e);
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
}
@Override
public void updateHomeStatus(Integer homeId, String status, Integer userId) {
log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, status);

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix the log injection vulnerability, sanitize the status parameter before logging it. The simplest and most robust way is to remove any newline (\n, \r) and other control characters from the string before it is logged. This can be done by replacing these characters with an empty string using String.replaceAll("[\\r\\n]", ""). The fix should be applied in the updateHomeStatus method in HomeServiceImpl.java, on line 374, by introducing a sanitized version of status for logging. No new methods or imports are required, as this uses standard Java string methods.


Suggested changeset 1
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
--- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
+++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
@@ -371,7 +371,8 @@
 
       @Override
       public void updateHomeStatus(Integer homeId, String status, Integer userId) {
-          log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, status);
+          String sanitizedStatus = status == null ? "" : status.replaceAll("[\\r\\n]", "");
+          log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, sanitizedStatus);
       }
 
       @Override
EOF
@@ -371,7 +371,8 @@

@Override
public void updateHomeStatus(Integer homeId, String status, Integer userId) {
log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, status);
String sanitizedStatus = status == null ? "" : status.replaceAll("[\\r\\n]", "");
log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, sanitizedStatus);
}

@Override
Copilot is powered by AI and may make mistakes. Always verify output.
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: 7

🔭 Outside diff range comments (1)
src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java (1)

566-571: 민감정보(주소/이름 등) 전체 JSON 로그 출력 — PII 누출 위험

요청 전체를 JSON으로 디버깅 로그에 남기면 주소/이름 등이 노출됩니다. 최소 필드만 로깅하거나 마스킹/비활성화 하세요.

안전한 로깅 예:

-            try {
-                ObjectMapper mapper = new ObjectMapper();
-                String requestJson = mapper.writeValueAsString(aiRequest);
-                log.debug("[디버깅] AI 서버 전송 데이터: {}", requestJson);
-            } catch (Exception e) {
-                log.error("AI 요청 데이터 JSON 변환 실패", e);
-            }
+            log.debug(
+                "[디버깅] AI 서버 전송 - homeId: {}, propertyPrice: {}, monthlyRent: {}",
+                LogSanitizerUtil.sanitizeValue(aiRequest.getHomeId()),
+                LogSanitizerUtil.sanitizeValue(aiRequest.getPropertyPrice()),
+                LogSanitizerUtil.sanitizeValue(aiRequest.getMonthlyRent())
+            );
♻️ Duplicate comments (2)
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (2)

6-16: insertHome에 created_at/updated_at 미포함 — 생성/수정 시각이 DB에 저장되지 않습니다

서비스/엔티티에서 값을 세팅하더라도 INSERT에 포함되지 않아 DB에 저장 누락될 수 있습니다. DB 기본값이 없다면 NOW()로 직접 채우거나 파라미터로 전달하세요.

적용 예시:

     <insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
         INSERT INTO home (
             user_id, addr1, addr2, residence_type, lease_type,
             deposit_price, monthly_rent, maintenance_fee, home_status,
-            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area
+            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area,
+            created_at, updated_at
         ) VALUES (
                      #{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
-                     #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
-                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}
+                     #{depositPrice}, #{monthlyRent}, #{maintenanceFee}, #{homeStatus},
+                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea},
+                     NOW(), NOW()
         )
     </insert>

350-352: homeDirection 바인딩 불일치 — 카운트/검색 결과 엇갈림 가능

리스트 쿼리는 #{search.homeDirection.getKey()}를, 카운트는 #{search.homeDirection}를 사용합니다. 동일하게 맞추세요.

-                AND hd.home_direction = #{search.homeDirection}
+                AND hd.home_direction = #{search.homeDirection.getKey()}
🧹 Nitpick comments (6)
src/main/java/org/scoula/global/config/ServletConfig.java (1)

81-94: 전역 String→LocalDate 컨버터 기능 확장 제안 및 LocalDateTime 컨버터 검토 요청

현재 ISO_LOCAL_DATE만 지원하며, 빈 문자열/공백만 null 처리하고 있습니다. 폼(form-urlencoded/multipart) LocalDate 변환 로직에 아래 보완을 권장합니다:

  • source.trim() 후 empty 또는 "null"(대소문자 무시) 문자열 null 처리
  • DateTimeFormatter 배열로 다중 포맷(ISO_LOCAL_DATE, yyyy/M/d, yyyy.MM.dd 등) 순회 파싱
  • 파싱 실패 시 DateTimeParseException 무시 후, 최종적으로 IllegalArgumentException으로 예외 래핑

샘플 개선 코드:

@Override
public void addFormatters(FormatterRegistry registry) {
-   registry.addConverter(
-       new Converter<String, LocalDate>() {
-           @Override
-           public LocalDate convert(String source) {
-               if (source == null || source.trim().isEmpty()) {
-                   return null;
-               }
-               return LocalDate.parse(source, DateTimeFormatter.ISO_LOCAL_DATE);
-           }
-       });
+   registry.addConverter(new Converter<String, LocalDate>() {
+       @Override
+       public LocalDate convert(String source) {
+           if (source == null) {
+               return null;
+           }
+           String s = source.trim();
+           if (s.isEmpty() || "null".equalsIgnoreCase(s)) {
+               return null;
+           }
+           DateTimeFormatter[] formats = {
+               DateTimeFormatter.ISO_LOCAL_DATE,
+               DateTimeFormatter.ofPattern("yyyy/M/d"),
+               DateTimeFormatter.ofPattern("yyyy.MM.dd")
+           };
+           for (DateTimeFormatter fmt : formats) {
+               try {
+                   return LocalDate.parse(s, fmt);
+               } catch (DateTimeParseException ignored) {
+               }
+           }
+           throw new IllegalArgumentException("Invalid LocalDate format: " + source);
+       }
+   });
}

검토 요청 사항:

  • src/main/java/org/scoula/global/config/ServletConfig.java에 위 개선 로직 반영
  • 폼 데이터 바인딩 시 LocalDateTime 파라미터를 직접 사용하는 컨트롤러가 있는지 확인 → 별도 Converter<String, LocalDateTime> 또는 Formatter<LocalDateTime> 등록 필요 여부 검토
  • 개별 포맷 지정이 필요한 경우, Formatter<LocalDate> 또는 Formatter<LocalDateTime> 활용 고려
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (3)

100-105: 조건 검색에서 본인인증(EXISTS) 필터 누락 — 노출 정책 불일치 가능

selectHomeList에는 본인인증 EXISTS 필터가 있으나, selectHomeListByCondition에는 없습니다. 목록/조건검색 정책을 일치시키는 것이 안전합니다.

         <where>
             h.home_status = 'AVAILABLE'
+            AND EXISTS (
+                SELECT 1
+                FROM home_identity_verification hiv
+                WHERE h.home_id = hiv.home_id
+            )

본인인증 필터를 조건검색에도 적용하는 것이 요구사항에 맞는지 확인 부탁드립니다.


256-259: findHomeById: SELECT * 및 중복 API — selectHomeById로 통일 권장

현재 findHomeById는 별칭 없이 SELECT *를 사용하고, selectHomeById는 별칭/조인을 사용합니다. 매핑 안정성과 일관성을 위해 서비스에서 selectHomeById로 통일하고 findHomeById는 제거/Deprecated 권장합니다.

원하시면 서비스/매퍼 호출부까지 포함해 통일 PR 패치 드리겠습니다.


294-301: parameterType="int"와 @param 혼용 — 불필요/혼동 소지

단일 파라미터에 @param을 사용하면 MyBatis가 ParamMap을 사용하므로 parameterType 명시는 불필요합니다. 제거하여 일관성을 높이세요.

예:

-    <delete id="deleteHome" parameterType="int">
+    <delete id="deleteHome">
         DELETE FROM home WHERE home_id = #{homeId}
     </delete>

동일 패턴의 다른 delete에도 적용 권장.

Also applies to: 302-305, 306-309

src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1)

50-51: 매물 상태 파라미터는 Enum(HomeStatus) 사용 권장

현재 문자열 status를 직접 전달합니다. Enum을 사용하고 TypeHandler(혹은 MyBatis EnumTypeHandler 기본 설정)를 활용하면 오탈자/유효성 문제를 줄일 수 있습니다.

src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java (1)

349-353: 주소 디버그 로그 마스킹 권장

주소는 PII입니다. 디버그라도 마스킹/비활성화가 안전합니다.

-          log.debug(
-                  "[디버깅] 주소 결정 로직 - request.address: {}, registry.roadAddress: {}, final.address: {}",
-                  request.getAddress(),
-                  address,
-                  finalAddress);
+          log.debug(
+                  "[디버깅] 주소 결정 로직 - final.address.length: {}",
+                  LogSanitizerUtil.sanitizeValue(finalAddress != null ? finalAddress.length() : 0));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df28b50 and 742e2d8.

📒 Files selected for processing (10)
  • src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java (2 hunks)
  • src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java (0 hunks)
  • src/main/java/org/scoula/domain/home/dto/request/HomeReportRequestDto.java (0 hunks)
  • src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java (0 hunks)
  • src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java (0 hunks)
  • src/main/java/org/scoula/domain/home/dto/response/MaintenanceFeeItemResponseDto.java (0 hunks)
  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1 hunks)
  • src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java (0 hunks)
  • src/main/java/org/scoula/global/config/ServletConfig.java (2 hunks)
  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (1 hunks)
💤 Files with no reviewable changes (6)
  • src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java
  • src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java
  • src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java
  • src/main/java/org/scoula/domain/home/dto/response/MaintenanceFeeItemResponseDto.java
  • src/main/java/org/scoula/domain/home/dto/request/HomeReportRequestDto.java
  • src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/org/scoula/domain/*/mapper/*Mapper.java

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/java/org/scoula/domain/*/mapper/*Mapper.java: Define MyBatis mapper interfaces under src/main/java/org/scoula/domain/*/mapper/*Mapper.java
Annotate MyBatis mapper interfaces with @Mapper

Files:

  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java
src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml: Place MyBatis XML mappers at src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml
Name MyBatis XML mappers *Mapper.xml to match their corresponding *Mapper.java interfaces

Files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
🧠 Learnings (2)
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/java/org/scoula/domain/*/mapper/*Mapper.java : Define MyBatis mapper interfaces under `src/main/java/org/scoula/domain/*/mapper/*Mapper.java`

Applied to files:

  • src/main/java/org/scoula/domain/home/mapper/HomeMapper.java
📚 Learning: 2025-08-12T03:59:30.512Z
Learnt from: CR
PR: ITZEEP/backend#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-12T03:59:30.512Z
Learning: Applies to src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml : Name MyBatis XML mappers `*Mapper.xml` to match their corresponding `*Mapper.java` interfaces

Applied to files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
🧬 Code Graph Analysis (1)
src/main/java/org/scoula/global/config/ServletConfig.java (1)
src/main/java/org/scoula/global/config/RootConfig.java (1)
  • Configuration (20-70)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CodeQL Security Analysis (java)
🔇 Additional comments (3)
src/main/java/org/scoula/global/config/ServletConfig.java (2)

3-4: LocalDate 바인딩을 위한 Java Time import 추가, 적절합니다

폼 데이터 바인딩 시 LocalDate 변환을 위해 필요한 의존성입니다. JSON 직렬화/역직렬화는 RootConfig의 JavaTimeModule 설정으로 처리되고 있어 충돌 우려도 없습니다.


10-11: 전역 컨버터 등록을 위한 Spring Converter/FormatterRegistry import 추가 OK

addFormatters 구현에 필요한 최소 import만 추가되어 있어 깔끔합니다.

src/main/java/org/scoula/domain/home/mapper/HomeMapper.java (1)

58-67: 매퍼 시그니처 전반은 XML과 잘 정합됩니다

이미지 CRUD 시그니처/파라미터 바인딩이 XML과 일치합니다.

Comment on lines +311 to +316
HomeVO home = null;
try {
home = homeMapper.findHomeById(homeId);
} catch (Exception e) {
throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

매물 미존재 시 NPE 가능 — 존재 확인 후 비즈니스 예외로 변환하세요

findHomeById가 null을 반환해도 try/catch로 잡히지 않아 바로 NPE가 발생합니다. null 체크 후 INVALID_HOME_ID로 변환하세요. 또한 VO 매핑 안정성과 사용자명 조회를 위해 selectHomeById 사용을 권장합니다.

-        HomeVO home = null;
-        try {
-            home = homeMapper.findHomeById(homeId);
-        } catch (Exception e) {
-            throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
-        }
+        HomeVO home = homeMapper.selectHomeById(homeId != null ? homeId.intValue() : null);
+        if (home == null) {
+            throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
+        }
📝 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
HomeVO home = null;
try {
home = homeMapper.findHomeById(homeId);
} catch (Exception e) {
throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
}
HomeVO home = homeMapper.selectHomeById(homeId != null ? homeId.intValue() : null);
if (home == null) {
throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java
around lines 311 to 316, findHomeById may return null causing an NPE that the
current try/catch won’t catch; change the flow to call the mapper method that
returns a safe DTO (use selectHomeById as recommended), then check the returned
object for null and if null throw new
BusinessException(FraudErrorCode.INVALID_HOME_ID); ensure you perform VO mapping
only after null-check and use the selectHomeById result for stable field access
(including owner/username lookup) to avoid NPEs.


/** 사용자 이름 조회 (identity_verification 테이블에서) */
String findUserNameById(@Param("userId") Long userId);
HomeVO findHomeById(@Param("id") Long id);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ID 타입(Long) 혼재 및 find/select 이원화 — Integer/메소드 명 통일 필요

PR 요약상 ID 타입을 Integer로 통일한 것으로 보이는데, 여기만 Long이며 메소드명도 find/select로 이원화되어 있습니다. 서비스 사용처까지 selectHomeById(Integer)로 통일하는 것이 유지보수에 유리합니다.

원하시면 AiFraudAnalyzerService 호출부까지 포함해 변경 패치 드리겠습니다.

🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/home/mapper/HomeMapper.java around line 13,
the mapper method uses Long and a "find" prefix while the project standardized
IDs to Integer and uses "select" naming; change the method signature to HomeVO
selectHomeById(@Param("id") Integer id, update any MyBatis XML/annotations to
expect Integer, and rename usages across the codebase (service, controllers,
AiFraudAnalyzerService call sites, tests) to call selectHomeById(Integer) so
types and naming are consistent; also adjust imports and recompile to ensure no
type mismatches.

0, 0, 0, 0,
#{home.roomCnt}, NOW(), NOW()
#{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
#{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

'maintenaceFee' 오탈자 — 바인딩/매핑 실패로 NPE/SQL 오류 가능

컬럼/파라미터/별칭 전반에 'maintenaceFee' 오탈자가 반복됩니다. HomeVO가 maintenanceFee를 사용한다면 현재 매핑은 동작하지 않습니다(INSERT/UPDATE null 바인딩, SELECT 매핑 누락).

아래와 같이 일괄 수정하세요.

-                     #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
+                     #{depositPrice}, #{monthlyRent}, #{maintenanceFee}, #{homeStatus},
-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,
-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,
-        h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+        h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,
-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,
-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,
-                        maintenance_fee = #{maintenaceFee},
+                        maintenance_fee = #{maintenanceFee},

Also applies to: 54-54, 76-76, 93-93, 233-233, 248-248, 268-268

🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines
13, 54, 76, 93, 233, 248 and 268 there is a repeated typo "maintenaceFee" that
breaks parameter binding and result mapping; rename every occurrence of
"maintenaceFee" to "maintenanceFee" (update SQL INSERT/UPDATE parameter
placeholders, SELECT aliases, and any resultMap property names to match
HomeVO.maintenanceFee) so bindings and mappings align, then re-run mapper
tests/queries to confirm no null bindings or mapping misses remain.

#{isParkingAvailable}
#{homeId}, #{buildDate}, #{homeFloor}, #{buildingTotalFloors},
#{homeDirection}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory}
)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

home_direction Enum 저장/수정 시 getKey() 일관 적용 필요

조건 검색에서는 #{search.homeDirection.getKey()}를 사용하면서, INSERT/UPDATE는 #{homeDirection}로 저장 중입니다. TypeHandler가 없다면 의도한 키값이 아닌 toString() 결과가 저장될 수 있습니다.

-                     #{homeDirection}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory}
+                     #{homeDirection.getKey()}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory}
-                               home_direction = #{homeDirection},
+                               home_direction = #{homeDirection.getKey()},

Also applies to: 281-281

🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around line
25 (and also apply the same change at line 281), the INSERT/UPDATE parameter for
the home_direction enum is using #{homeDirection} while search uses
#{search.homeDirection.getKey()}; replace #{homeDirection} with
#{homeDirection.getKey()} (and likewise for any other enum fields in these
statements) to ensure the enum key is stored consistently, or alternatively wire
a proper TypeHandler for those enums and update the mappings to use the handler.

Comment on lines +63 to 69
SELECT
home_detail_id, home_id, build_date, home_floor, building_total_floors,
home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking
FROM home_detail
WHERE home_id = #{homeId}
</select>

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

selectHomeDetailByHomeId에 area/land_category 누락 — 조회 시 필드 손실

INSERT에서 area, land_category를 저장하지만 SELECT에 포함되지 않아 VO로 역매핑되지 않습니다.

         SELECT
-            home_detail_id, home_id, build_date, home_floor, building_total_floors,
-            home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking
+            home_detail_id as homeDetailId, home_id as homeId, build_date as buildDate,
+            home_floor as homeFloor, building_total_floors as buildingTotalFloors,
+            home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking,
+            area, land_category as landCategory
         FROM home_detail
         WHERE home_id = #{homeId}
📝 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
<select id="selectHomeDetailByHomeId" parameterType="int" resultType="org.scoula.domain.home.vo.HomeDetailVO">
SELECT
home_detail_id, home_id, build_date, home_floor, building_total_floors,
home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking
FROM home_detail
WHERE home_id = #{homeId}
</select>
<select id="selectHomeDetailByHomeId" parameterType="int" resultType="org.scoula.domain.home.vo.HomeDetailVO">
SELECT
home_detail_id as homeDetailId, home_id as homeId, build_date as buildDate,
home_floor as homeFloor, building_total_floors as buildingTotalFloors,
home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking,
area, land_category as landCategory
FROM home_detail
WHERE home_id = #{homeId}
</select>
🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines
63 to 69, the SELECT for selectHomeDetailByHomeId omits the area and
land_category columns so they are not populated into the HomeDetailVO; update
the SELECT to include area and land_category and, if the VO uses camelCase
properties, add appropriate aliases (e.g., area, land_category AS landCategory)
so the result columns map correctly back to HomeDetailVO.

Comment on lines +154 to +179
<when test="search.sortBy != null and search.sortBy != ''">
ORDER BY
<if test="search.sortBy == 'price'">
h.deposit_price, h.monthly_rent
</if>
<if test="search.sortBy == 'viewCnt'">
h.view_cnt
</if>
<if test="search.sortBy == 'likeCnt'">
h.like_cnt
</if>
<if test="search.sortBy == 'createdAt'">
h.created_at
</if>
<if test="search.sortDirection == 'DESC'">
DESC
</if>
<if test="search.sortDirection == 'ASC'">
ASC
</if>
</when>
<otherwise>
ORDER BY h.created_at DESC
</otherwise>
</choose>
LIMIT #{search.size} OFFSET #{search.offset}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

ORDER BY 동적 구성 취약 — 알 수 없는 sortBy 시 SQL 문법오류, price 정렬 방향 혼합

  • sortBy가 지정됐지만 매칭되는 가 없으면 "ORDER BY DESC" 같은 문법 오류가 납니다.
  • price 정렬에서 "h.deposit_price, h.monthly_rent DESC"는 첫 컬럼 ASC, 두 번째만 DESC가 됩니다.

안전한 화이트리스트와 방향 반복을 적용하세요.

-        <choose>
-            <when test="search.sortBy != null and search.sortBy != ''">
-                ORDER BY
-                <if test="search.sortBy == 'price'">
-                    h.deposit_price, h.monthly_rent
-                </if>
-                <if test="search.sortBy == 'viewCnt'">
-                    h.view_cnt
-                </if>
-                <if test="search.sortBy == 'likeCnt'">
-                    h.like_cnt
-                </if>
-                <if test="search.sortBy == 'createdAt'">
-                    h.created_at
-                </if>
-                <if test="search.sortDirection == 'DESC'">
-                    DESC
-                </if>
-                <if test="search.sortDirection == 'ASC'">
-                    ASC
-                </if>
-            </when>
-            <otherwise>
-                ORDER BY h.created_at DESC
-            </otherwise>
-        </choose>
+        <choose>
+            <when test="search.sortBy == 'price'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.deposit_price ASC, h.monthly_rent ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.deposit_price DESC, h.monthly_rent DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'viewCnt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.view_cnt ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.view_cnt DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'likeCnt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.like_cnt ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.like_cnt DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'createdAt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.created_at ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.created_at DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <otherwise>
+                ORDER BY h.created_at DESC
+            </otherwise>
+        </choose>
🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines
154 to 179, the ORDER BY generation can produce invalid SQL when search.sortBy
is set to an unknown value (resulting in "ORDER BY DESC") and the price case
mixes directions so only the second column gets DESC; fix by implementing a
whitelist mapping for allowed sortBy values (e.g., price -> "h.deposit_price,
h.monthly_rent", viewCnt -> "h.view_cnt", likeCnt -> "h.like_cnt", createdAt ->
"h.created_at"), only output ORDER BY when the sortBy matches the whitelist,
validate/normalize search.sortDirection to either 'ASC' or 'DESC' (default to
DESC), and append the direction after every column in the mapped value (or
repeat the direction for each column) so both deposit_price and monthly_rent
receive the same direction.

Comment on lines +314 to +320
SELECT COUNT(h.home_id)
FROM home h
LEFT JOIN home_detail hd ON h.home_id = hd.home_id
<where>
h.home_status = 'AVAILABLE'
<if test="search.residenceType != null">
AND h.residence_type = #{search.residenceType}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

조건 카운트에도 본인인증(EXISTS) 필터 반영 필요

목록/카운트 일관성 유지를 위해 selectHomeCountByCondition에도 동일한 EXISTS 조건을 추가하세요.

         <where>
             h.home_status = 'AVAILABLE'
+            AND EXISTS (
+                SELECT 1
+                FROM home_identity_verification hiv
+                WHERE h.home_id = hiv.home_id
+            )
📝 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
<select id="selectHomeCountByCondition" resultType="int">
SELECT COUNT(h.home_id)
FROM home h
LEFT JOIN home_detail hd ON h.home_id = hd.home_id
<where>
h.home_status = 'AVAILABLE'
<if test="search.residenceType != null">
<select id="selectHomeCountByCondition" resultType="int">
SELECT COUNT(h.home_id)
FROM home h
LEFT JOIN home_detail hd ON h.home_id = hd.home_id
<where>
h.home_status = 'AVAILABLE'
AND EXISTS (
SELECT 1
FROM home_identity_verification hiv
WHERE h.home_id = hiv.home_id
)
<if test="search.residenceType != null">
🤖 Prompt for AI Agents
In src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines
314-320, the selectHomeCountByCondition query is missing the same EXISTS (본인인증)
filter used by the list query which causes count/list inconsistency; add the
identical EXISTS subquery condition into this select (inside the <where> block,
alongside h.home_status and other <if> tests) using the same parameter bindings
and logic as the list query so the count matches the filtered list results.

"매물 검색 요청: residenceType={}, leaseType={}, addr1={}",
searchDTO.getResidenceType(),
searchDTO.getLeaseType(),
searchDTO.getAddr1());

Check failure

Code scanning / CodeQL

Log Injection High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 7 months ago

To fix this log injection vulnerability, we should sanitize the user-provided value (addr1) before logging it. The most straightforward and effective way is to remove or replace newline (\n, \r) and other control characters from the string before it is logged. This can be done by creating a helper method (e.g., sanitizeForLog) that replaces or removes such characters. The fix should be applied directly in the logging statement in the searchHomes method, specifically for searchDTO.getAddr1(). The helper method can be defined as a private static method within the same class. No external dependencies are required.


Suggested changeset 1
src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
--- a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
+++ b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
@@ -127,7 +127,7 @@
                   "매물 검색 요청: residenceType={}, leaseType={}, addr1={}",
                   searchDTO.getResidenceType(),
                   searchDTO.getLeaseType(),
-                  searchDTO.getAddr1());
+                  sanitizeForLog(searchDTO.getAddr1()));
           log.info("최대 월세 필터 값: {}", searchDTO.getMaxMonthlyRent());
 
           List<HomeResponseDTO> homes = homeService.searchHomes(searchDTO);
@@ -200,6 +200,17 @@
           return ResponseEntity.ok(ApiResponse.success(null, "매물이 성공적으로 수정되었습니다."));
       }
 
+      /**
+       * Sanitizes a string for safe logging by removing CR and LF characters.
+       */
+      private static String sanitizeForLog(String input) {
+          if (input == null) {
+              return null;
+          }
+          // Remove carriage return and line feed characters
+          return input.replaceAll("[\\r\\n]", "");
+      }
+
       @Override
       @DeleteMapping("/{homeId}")
       public ResponseEntity<ApiResponse<Void>> deleteHome(
EOF
@@ -127,7 +127,7 @@
"매물 검색 요청: residenceType={}, leaseType={}, addr1={}",
searchDTO.getResidenceType(),
searchDTO.getLeaseType(),
searchDTO.getAddr1());
sanitizeForLog(searchDTO.getAddr1()));
log.info("최대 월세 필터 값: {}", searchDTO.getMaxMonthlyRent());

List<HomeResponseDTO> homes = homeService.searchHomes(searchDTO);
@@ -200,6 +200,17 @@
return ResponseEntity.ok(ApiResponse.success(null, "매물이 성공적으로 수정되었습니다."));
}

/**
* Sanitizes a string for safe logging by removing CR and LF characters.
*/
private static String sanitizeForLog(String input) {
if (input == null) {
return null;
}
// Remove carriage return and line feed characters
return input.replaceAll("[\\r\\n]", "");
}

@Override
@DeleteMapping("/{homeId}")
public ResponseEntity<ApiResponse<Void>> deleteHome(
Copilot is powered by AI and may make mistakes. Always verify output.
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: 2

♻️ Duplicate comments (13)
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (13)

6-16: INSERT 컬럼/값 불일치: created_at/updated_at 누락 + maintenaceFee 오탈자

  • 감사/정렬에 필요한 created_at, updated_at이 INSERT에 포함되어 있지 않습니다. DB default가 없다면 NOW()로 채우거나 파라미터로 전달해야 합니다.
  • 파라미터/별칭에 오탈자(maintenaceFee)가 있어 바인딩 실패 위험이 큽니다.

아래와 같이 수정 제안드립니다.

     <insert id="insertHome" parameterType="org.scoula.domain.home.vo.HomeVO" useGeneratedKeys="true" keyProperty="homeId">
         INSERT INTO home (
             user_id, addr1, addr2, residence_type, lease_type,
             deposit_price, monthly_rent, maintenance_fee, home_status,
-            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area
+            view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area, created_at, updated_at
         ) VALUES (
                      #{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType},
-                     #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus},
-                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}
+                     #{depositPrice}, #{monthlyRent}, #{maintenanceFee}, #{homeStatus},
+                     #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea}, NOW(), NOW()
         )
     </insert>

18-27: Enum 저장 일관성: homeDirection는 getKey()로 저장하거나 TypeHandler 사용 필요

검색에서는 homeDirection.getKey()를 사용하지만 INSERT는 #{homeDirection}로 저장 중입니다. TypeHandler가 없다면 의도와 다른 값이 저장될 수 있습니다.

-                     #{homeDirection}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory}
+                     #{homeDirection.getKey()}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory}

추가로 Enum에 대한 TypeHandler 사용 여부(HomeDirection 등)를 확인 부탁드립니다. 필요 시 전역 TypeHandler로 일관성 확보를 권장합니다.


49-61: SELECT 별칭 오탈자: maintenaceFee → maintenanceFee

VO 매핑 실패를 유발합니다. 다음과 같이 수정하세요.

-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,

63-69: HomeDetail 조회에 area/land_category 누락 + 명시적 alias 권장

INSERT로 저장한 area, land_category가 SELECT에 누락되어 VO 매핑이 되지 않습니다. 또한 camelCase 매핑 보장을 위해 명시 alias 권장합니다.

-        SELECT
-            home_detail_id, home_id, build_date, home_floor, building_total_floors,
-            home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking
-        FROM home_detail
+        SELECT
+            home_detail_id as homeDetailId, home_id as homeId, build_date as buildDate,
+            home_floor as homeFloor, building_total_floors as buildingTotalFloors,
+            home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking,
+            area, land_category as landCategory
+        FROM home_detail
         WHERE home_id = #{homeId}

73-81: SELECT 별칭 오탈자: maintenaceFee → maintenanceFee

목록 조회에서도 동일 오탈자가 반복됩니다.

-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,

135-137: homeDirection 바인딩 불일치 — 목록/카운트 불일치 위험

검색 쿼리는 Enum 키를 비교해야 합니다. getKey() 사용으로 일치성 확보가 필요합니다.

-                AND hd.home_direction = #{search.homeDirection}
+                AND hd.home_direction = #{search.homeDirection.getKey()}

154-179: ORDER BY 동적 구성 취약 — 알 수 없는 sortBy 시 SQL 오류/정렬 방향 혼합

화이트리스트 방식과 방향 반복으로 안전한 정렬식을 구성하세요.

-        <choose>
-            <when test="search.sortBy != null and search.sortBy != ''">
-                ORDER BY
-                <if test="search.sortBy == 'price'">
-                    h.deposit_price, h.monthly_rent
-                </if>
-                <if test="search.sortBy == 'viewCnt'">
-                    h.view_cnt
-                </if>
-                <if test="search.sortBy == 'likeCnt'">
-                    h.like_cnt
-                </if>
-                <if test="search.sortBy == 'createdAt'">
-                    h.created_at
-                </if>
-                <if test="search.sortDirection == 'DESC'">
-                    DESC
-                </if>
-                <if test="search.sortDirection == 'ASC'">
-                    ASC
-                </if>
-            </when>
-            <otherwise>
-                ORDER BY h.created_at DESC
-            </otherwise>
-        </choose>
+        <choose>
+            <when test="search.sortBy == 'price'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.deposit_price ASC, h.monthly_rent ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.deposit_price DESC, h.monthly_rent DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'viewCnt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.view_cnt ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.view_cnt DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'likeCnt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.like_cnt ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.like_cnt DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <when test="search.sortBy == 'createdAt'">
+                <choose>
+                    <when test="search.sortDirection == 'ASC'">
+                        ORDER BY h.created_at ASC
+                    </when>
+                    <otherwise>
+                        ORDER BY h.created_at DESC
+                    </otherwise>
+                </choose>
+            </when>
+            <otherwise>
+                ORDER BY h.created_at DESC
+            </otherwise>
+        </choose>

228-236: SELECT 별칭 오탈자: maintenaceFee → maintenanceFee

좋아요 목록 조회에서도 동일 오탈자입니다.

-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,

243-251: SELECT 별칭 오탈자: maintenaceFee → maintenanceFee

사용자 매물 목록 조회에서도 동일 오탈자입니다.

-            h.maintenance_fee as maintenaceFee, h.home_status as homeStatus,
+            h.maintenance_fee as maintenanceFee, h.home_status as homeStatus,

260-275: UPDATE 파라미터 오탈자: maintenaceFee → maintenanceFee

업데이트 시 바인딩 실패로 값이 반영되지 않습니다.

-                        maintenance_fee = #{maintenaceFee},
+                        maintenance_fee = #{maintenanceFee},

참고: updated_at은 서비스에서 세팅하지 않는 경우 DB에서 NOW()로 관리하도록 변경하는 것도 고려 가능합니다.


276-287: HomeDetail 업데이트: homeDirection 일관성 + area/land_category 업데이트 누락

  • Enum 저장은 getKey() 사용 또는 TypeHandler 필요
  • area, land_category 필드가 INSERT에는 있지만 UPDATE에는 없습니다.
     <update id="updateHomeDetail" parameterType="org.scoula.domain.home.vo.HomeDetailVO">
         UPDATE home_detail SET
                                build_date = #{buildDate},
                                home_floor = #{homeFloor},
                                building_total_floors = #{buildingTotalFloors},
-                               home_direction = #{homeDirection},
+                               home_direction = #{homeDirection.getKey()},
                                bathroom_count = #{bathroomCnt},
                                is_pet = #{isPet},
-                               is_parking_available = #{isParking}
+                               is_parking_available = #{isParking},
+                               area = #{area},
+                               land_category = #{landCategory}
         WHERE home_detail_id = #{homeDetailId}
     </update>

314-369: 조건 카운트에도 본인인증(EXISTS) 필터 반영 + homeDirection 바인딩 일치화

  • 리스트/카운트 일관성 유지를 위해 EXISTS 조건 필요
  • homeDirection은 getKey()로 비교해야 목록과 일치
         <where>
             h.home_status = 'AVAILABLE'
+            AND EXISTS (
+                SELECT 1
+                FROM home_identity_verification hiv
+                WHERE h.home_id = hiv.home_id
+            )
             <if test="search.residenceType != null">
                 AND h.residence_type = #{search.residenceType}
             </if>
@@
-            <if test="search.homeDirection != null">
-                AND hd.home_direction = #{search.homeDirection}
-            </if>
+            <if test="search.homeDirection != null">
+                AND hd.home_direction = #{search.homeDirection.getKey()}
+            </if>

310-313: 카운트와 목록 정책 불일치: 본인인증(EXISTS) 필터 미적용

selectHomeList는 본인인증 EXISTS를 적용하지만 카운트는 미적용입니다. 페이지네이션 토탈이 어긋납니다.

-    <select id="selectHomeCount" resultType="int">
-        SELECT COUNT(*) FROM home WHERE home_status = 'AVAILABLE'
-    </select>
+    <select id="selectHomeCount" resultType="int">
+        SELECT COUNT(*)
+        FROM home h
+        WHERE h.home_status = 'AVAILABLE'
+          AND EXISTS (
+            SELECT 1
+            FROM home_identity_verification hiv
+            WHERE h.home_id = hiv.home_id
+          )
+    </select>
🧹 Nitpick comments (1)
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (1)

83-85: EXISTS 성능/관례 개선: SELECT 1 사용 권장

EXISTS 내 select * 대신 select 1 사용을 권장합니다. 기능 차이는 없으나 의도 표기가 선명하고 DB에 따라 최적화에 유리합니다.

-        WHERE h.home_status = 'AVAILABLE' AND EXISTS(select * from home_identity_verification as hiv where h.home_id = hiv.home_id)
+        WHERE h.home_status = 'AVAILABLE' AND EXISTS (SELECT 1 FROM home_identity_verification AS hiv WHERE h.home_id = hiv.home_id)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0979b4f and 81a97dc.

📒 Files selected for processing (2)
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java (1 hunks)
  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java
🧰 Additional context used
📓 Path-based instructions (1)
src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml

📄 CodeRabbit Inference Engine (CLAUDE.md)

src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml: Place MyBatis XML mappers at src/main/resources/org/scoula/domain/*/mapper/*Mapper.xml
Name MyBatis XML mappers *Mapper.xml to match their corresponding *Mapper.java interfaces

Files:

  • src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Coverage
  • GitHub Check: CodeQL Security Analysis (java)
🔇 Additional comments (3)
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml (3)

294-309: 파라미터 바인딩 검증 완료: @param 어노테이션이 적용되어 있습니다

HomeMapper 인터페이스의 해당 메서드들은 모두 아래와 같이 @param으로 명시된 파라미터 이름을 사용하고 있으므로, XML에서 #{homeId} / #{homeDetailId} 바인딩은 올바릅니다.
별도 수정 없이 현재대로 유지해 주세요.


85-85: 바인딩 검증 완료: LIMIT 및 OFFSET 파라미터가 올바르게 @param으로 바인딩되어 있습니다.

검증 결과, HomeMapper.java

List<HomeVO> selectHomeList(@Param("offset") int offset, @Param("limit") int limit);

시그니처에 @Param("offset")@Param("limit")이 모두 정의되어 있어 LIMIT #{limit} OFFSET #{offset} 구문은 정상 작동합니다.
이상 없습니다.


288-293: 해결: updateHomeStatus 파라미터 바인딩 정상 확인됨

  • HomeMapper.javaupdateHomeStatus 시그니처에 @Param("homeId") Integer homeId, @Param("status") String status가 모두 선언되어 있습니다.
  • XML의 #{homeId}, #{status} 바인딩은 문제없이 동작합니다.

추가 조치 불필요합니다.

Comment on lines +100 to +153
<where>
h.home_status = 'AVAILABLE'
<if test="search.residenceType != null">
AND h.residence_type = #{search.residenceType}
</if>
<if test="search.leaseType != null">
AND h.lease_type = #{search.leaseType}
</if>
<if test="search.addr1 != null and search.addr1 != ''">
AND h.addr1 LIKE CONCAT('%', #{search.addr1}, '%')
</if>
<if test="search.maxDepositPrice != null and search.maxDepositPrice &gt; 0">
AND h.deposit_price &lt;= #{search.maxDepositPrice}
</if>
<if test="search.maxMonthlyRent != null and search.maxMonthlyRent &gt; 0">
AND h.monthly_rent &lt;= #{search.maxMonthlyRent}
</if>
<if test="search.maxMaintenanceFee != null and search.maxMaintenanceFee &gt; 0">
AND h.maintenance_fee &lt;= #{search.maxMaintenanceFee}
</if>
<if test="search.minSupplyArea != null and search.minSupplyArea &gt; 0">
AND h.supply_area &gt;= #{search.minSupplyArea}
</if>
<if test="search.maxSupplyArea != null and search.maxSupplyArea &gt; 0">
AND h.supply_area &lt;= #{search.maxSupplyArea}
</if>
<if test="search.minRoomCnt != null">
AND h.room_cnt &gt;= #{search.minRoomCnt}
</if>
<if test="search.maxRoomCnt != null">
AND h.room_cnt &lt;= #{search.maxRoomCnt}
</if>
<if test="search.exclusiveArea != null">
AND h.exclusive_area= #{search.exclusiveArea}
</if>
<if test="search.homeDirection != null">
AND hd.home_direction = #{search.homeDirection}
</if>
<if test="search.homeFloor != null">
AND hd.home_floor = #{search.homeFloor}
</if>
<if test="search.minFloor != null">
AND hd.home_floor &gt;= #{search.minFloor}
</if>
<if test="search.maxFloor != null">
AND hd.home_floor &lt;= #{search.maxFloor}
</if>
<if test="search.isPet != null">
AND hd.is_pet = #{search.isPet}
</if>
<if test="search.isParking != null">
AND hd.is_parking_available = #{search.isParking}
</if>
</where>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

본인인증(EXISTS) 필터 누락 — 조건 목록이 기본 목록과 정책 불일치

AI 요약에 따르면 조건 검색도 본인인증 필터가 적용되어야 합니다. 현재 selectHomeListByCondition에는 EXISTS가 없습니다.

         <where>
             h.home_status = 'AVAILABLE'
+            AND EXISTS (
+                SELECT 1
+                FROM home_identity_verification hiv
+                WHERE h.home_id = hiv.home_id
+            )
🤖 Prompt for AI Agents
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml around lines 100
to 153: The WHERE clause for selectHomeListByCondition is missing the required
"본인인증" EXISTS filter; add an EXISTS subquery to the WHERE block that ensures the
home owner/member associated with h has a verified identity record (e.g. EXISTS
(select 1 from the member or member_identity table where member.id = h.member_id
and the verified flag/status = true)); place this clause alongside the other
<if> conditions (or unconditionally if always required) so only homes whose
owner is identity-verified are returned.

Comment on lines +256 to +259
SELECT * FROM home WHERE home_id=#{id}
</select>

<update id="updateHome" parameterType="org.scoula.domain.home.vo.HomeVO">
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

중복/구식 메서드 의심: findHomeById는 selectHomeById와 중복

동일 역할의 메서드가 공존하면 혼란/유지보수 비용이 증가합니다. 사용처 확인 후 제거 또는 통합을 권장합니다.

-    <select id="findHomeById" resultType="org.scoula.domain.home.vo.HomeVO">
-        SELECT * FROM home WHERE home_id=#{id}
-    </select>

다음 스크립트로 findHomeById 사용 여부를 확인하세요.


🏁 Script executed:

#!/bin/bash
# findHomeById 호출/참조 위치 검색
rg -n -C2 '\bfindHomeById\s*\('
rg -n -C2 '"findHomeById"'

Length of output: 1613


selectHomeById 존재 여부 확인을 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# selectHomeById 호출/참조 위치 검색
rg -n -C2 '\bselectHomeById\s*\('
rg -n -C2 '"selectHomeById"'
rg -n -C2 '<select id="selectHomeById"'

Length of output: 2863


중복된 매핑 메서드 제거/통합 필요 (findHomeById ⇄ selectHomeById)
findHomeByIdselectHomeById가 동일 기능을 수행하며, 서로 다른 파라미터 타입( Long vs Integer )만을 구분점으로 두고 있어 유지보수가 어렵습니다. 다음 사항을 반영해 하나로 통합하세요.

• src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml
<select id="findHomeById" …> 블록 제거
• src/main/java/org/scoula/domain/home/mapper/HomeMapper.java
HomeVO findHomeById(@Param("id") Long id); 메서드 시그니처 삭제
• src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java (라인 313)
homeMapper.findHomeById(homeId)homeMapper.selectHomeById(homeId)
• src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java
selectHomeById 호출부 검토 후 파라미터 타입( Integer homeId ) 일관성 유지

변경 후 전역 검색(rg)으로 호출부가 모두 변경되었는지 확인하고, 파라미터 타입 불일치로 인한 부작용이 없는지 테스트하세요.

🤖 Prompt for AI Agents
src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml lines 256-259:
remove the duplicate <select id="findHomeById"...> block and keep only
selectHomeById; then delete the corresponding method signature HomeVO
findHomeById(@Param("id") Long id) from
src/main/java/org/scoula/domain/home/mapper/HomeMapper.java, update the call in
src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java
(around line 313) replacing homeMapper.findHomeById(homeId) with
homeMapper.selectHomeById(homeId), and review/adjust usages in
src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java to ensure the
parameter type for selectHomeById is consistently Integer (or converted where
needed); finally run a global search to confirm no remaining findHomeById usages
and run tests to catch any type-mismatch regressions.

@MeongW MeongW merged commit e9763cb into develop Aug 17, 2025
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️ refactor 리팩토링 작업

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 매물 등록 및 조회

3 participants