Skip to content

[FEAT] 적법성 검사#86

Merged
MeongW merged 12 commits intodevelopfrom
feat/contract_fix
Aug 14, 2025
Merged

[FEAT] 적법성 검사#86
MeongW merged 12 commits intodevelopfrom
feat/contract_fix

Conversation

@minnieming
Copy link
Contributor

@minnieming minnieming commented Aug 14, 2025

🚀 관련 이슈

🔑 주요 변경사항

✔️ 체크 리스트

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

📢 To Reviewers

📸 스크린샷 or 실행영상

↗️ 개선 사항

Summary by CodeRabbit

  • 신기능

    • 계약 상세에 당사자·상대방 본인확인 정보가 함께 표시됩니다.
    • 합법성(legality) 관리 흐름 추가: 소유자 요청 등록·수정·삭제, 구매자 수락·거절, 특약 저장·적용을 지원합니다.
  • 변경 사항

    • 단일 조회 중심에서 다단계 워크플로로 전환되어 단계별 처리와 상태 표시가 강화되었습니다.
    • 일부 화면 문구와 액션 경로(특약 저장 등)가 최신 흐름에 맞게 변경되었습니다.

@minnieming minnieming requested a review from MeongW August 14, 2025 04:44
@minnieming minnieming self-assigned this Aug 14, 2025
@minnieming minnieming added the ✨ feature 새로운 기능 요청 label Aug 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

계약 적법성 워크플로가 다수의 엔드포인트로 확장되었습니다. 특약 저장/갱신, 소유자·구매자 적법성 요청 생성·삭제·거절 흐름과 Redis 기반 임시저장, IdentityVerification VO 통합으로 ContractDTO 매핑이 변경되었습니다.

Changes

Cohort / File(s) Summary
Controller: 엔드포인트 추가/재매핑
src/main/java/org/scoula/domain/contract/controller/ContractController.java, src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
특약 저장/업데이트/적법성 관련 신규 엔드포인트 추가: saveSpecialContract, updateOwnerLegality, deleteOwnerLegality, updateBuyerLegality, rejectBuyerLegality, sendStep4, updateSpecialContract. 기존 특약 저장 매핑 경로 변경(이전 /specialContract/save/special-contract) 및 API 설명(문구) 변경.
Service: 적법성 로직·영속성·통합
src/main/java/org/scoula/domain/contract/service/ContractService.java, src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
서비스 인터페이스 및 구현에 적법성 흐름 메서드 추가/삭제(saveSpecialContract, deleteOwnerLegality, updateOwnerLegality, updateBuyerLegality, rejectBuyerLegality 등). Redis에 LegalityRequestDTO JSON 저장/삭제, AI 메시지 알림 호출, IdentityVerificationService로 VO 조회 후 ContractDTO 빌드 로직 추가. 트랜잭션 주석 추가.
DTO / 매퍼 변경
src/main/java/org/scoula/domain/contract/dto/ContractDTO.java, src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java, src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java
ContractDTO.toDTO 시그니처 확장: (ContractMongoDocument, ownerVO, buyerVO)로 변경하여 주소/전화번호를 VO에서 매핑. 신규 DTO 추가: LegalityRequestDTO, UpdateLegalityDTO.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller as ContractController
  participant Service as ContractService
  participant ID as IdentityVerificationService
  participant Redis as RedisStore
  participant AI as AIServer

  Client->>Controller: GET /getContract (contractChatId)
  Controller->>Service: getContract(contractChatId, userId)
  Service->>ID: fetchDecrypted(ownerContractId, buyerContractId)
  ID-->>Service: ownerVO, buyerVO
  Service-->>Controller: ContractDTO(ownerVO, buyerVO 포함)
  Controller-->>Client: ApiResponse<ContractDTO>

  Client->>Controller: POST /suggest/legality (owner submits UpdateLegalityDTO)
  Controller->>Service: updateOwnerLegality(contractChatId, userId, dto)
  Service->>Redis: set(final-contract:legality:<chatId>:<ownerId>, JSON)
  Service->>AI: AiMessage(알림)
  Controller-->>Client: 200

  Client->>Controller: POST /update/legality (buyer applies SpecialContractUpdateDTO)
  Controller->>Service: updateBuyerLegality(contractChatId, userId, dto)
  Service->>Redis: get(...) -> LegalityRequestDTO
  Service->>Service: updateSpecialContract(...) (persist)
  Service->>Redis: delete(...)
  Service->>AI: AiMessage(완료 알림)
  Controller-->>Client: 200
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
홈 화면 UI 컴포넌트 구현: 헤더, 메인 배너, 제품 목록, 푸터 (#48) 백엔드 계약/적법성 API 변경으로, 프론트 UI 구현 목표와 무관합니다.
홈 화면 디자인 반영 및 네비게이션/슬라이더/링크 구성 (#48) 해당 PR에 프론트엔드 레이아웃·스타일 관련 코드가 없습니다.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
적법성 엔드포인트 추가·로직 (src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java : 여러 메서드 추가) 링크된 이슈(#48)는 프론트 홈 UI 구현이며, 이 변경은 백엔드 도메인·적법성 기능 확대라 관련 없음.
ContractDTO 시그니처 변경 및 VO 매핑 (src/main/java/org/scoula/domain/contract/dto/ContractDTO.java) UI 구성 이슈와 무관한 데이터 매핑/도메인 변경입니다.
Redis 저장/AI 메시지 통합 (src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java) 홈 화면 UI 요구사항과 관련이 없는 백엔드 통합 로직입니다.

Possibly related PRs

Suggested reviewers

  • MeongW
  • Whatdoyumin

Poem

토끼가 복사한 코드 한쪽,
VO 품에 넣고 콕콕 쌓아둔 빛.
적법성 길을 깡충깡충,
Redis에 쪽지 남기고 훌쩍.
머지되면 당근 파티 할래요 🥕✨

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 feat/contract_fix

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

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

394-452: PII 외부 전송 리스크: 최소 전송/마스킹/로깅 축소 필요

복호화된 신원정보(owner/buyer VO)가 포함된 ContractDTO 전체를 외부 AI 서버로 전송하고 있습니다. 이름/주민번호/전화/주소 등 민감 정보가 포함될 수 있으며, 개인정보보호 및 내부 보안 규정에 저촉될 수 있습니다.

권장 조치:

  • AI 서버에 필요한 필드만 포함한 전용 DTO(예: LegalityValidationRequest)를 정의하고 민감 필드는 마스킹(예: 이름 이니셜/전화 일부/주소 시군구 단위) 또는 제거.
  • 응답/요청 본문 로깅 최소화(현재 warn 레벨로 DTO 전체 문자열화). 민감 데이터는 로깅 금지 또는 요약 로그로 대체.
  • 외부 호출 타임아웃/재시도/서킷브레이커 구성(RestTemplate 설정)을 통해 장애 고립.
  • 전송 채널 TLS 보장 및 대상 도메인 화이트리스트 관리.

원하시면 전용 DTO와 매핑/마스킹 유틸, RestTemplate 커스터마이저(Timeout) 코드를 함께 제공하겠습니다.

🧹 Nitpick comments (8)
src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java (1)

13-13: 날짜 필드 타입을 LocalDateTime으로 변경 고려

createdAt 필드가 String 타입으로 정의되어 있는데, 날짜/시간 데이터는 LocalDateTime 또는 LocalDate 타입을 사용하는 것이 타입 안정성과 날짜 연산에 유리합니다.

+import java.time.LocalDateTime;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.scoula.global.common.constant.Constants;
+
 @Getter
 @Setter
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class LegalityRequestDTO {
       private String legalBasis;
       private Long requestId;
-      private String createdAt;
+      @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Constants.DateTime.DEFAULT_DATETIME_FORMAT)
+      private LocalDateTime createdAt;
 }
src/main/java/org/scoula/domain/contract/service/ContractService.java (1)

82-83: JavaDoc 주석 형식 수정 필요

JavaDoc 주석이 한 줄로 압축되어 있어 가독성이 떨어집니다. 표준 형식으로 수정이 필요합니다.

-      /** * step4 start 특약을 개약 테이블에 저장하기 * * @param contractChatId 채팅방 아이디 * @param userId 유저 아이디 */
+      /**
+       * step4 start 특약을 계약 테이블에 저장하기
+       *
+       * @param contractChatId 채팅방 아이디
+       * @param userId 유저 아이디
+       */
src/main/java/org/scoula/domain/contract/controller/ContractController.java (1)

111-116: 주석 제거 및 명확한 문서화 필요

"메서드만 만들어서 써도 될 듯"이라는 불필요한 주석이 포함되어 있습니다. 프로덕션 코드에서는 이런 임시 주석을 제거해야 합니다.

-      // 메서드만 만들어서 써도 될 듯
       @ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java (3)

103-111: HTTP 메서드/경로 네이밍 개선 제안: 조회는 GET + 리소스형 경로 사용

조회 목적 엔드포인트임에도 POST를 사용하고 있고, 경로명이 동사형(getContracts)입니다. REST 관점에서 GET /contracts 또는 GET /contract로의 정리가 바람직합니다. 기존 /getContract와의 중복도 고려해 하나만 유지하는 것을 권장합니다.

적용 제안(diff):

-      @PostMapping("/getContracts")
+      @GetMapping("/contract")
       public ResponseEntity<ApiResponse<ContractDTO>> getContracts(

참고: 기존 /getContract 엔드포인트와의 중복을 해소하려면 하나를 제거하거나 둘 중 하나를 대체하도록 리팩터링이 필요합니다. 원하시면 리소스 전체 경로/메서드 체계를 일괄 제안드리겠습니다.


132-139: 삭제 엔드포인트 경로 정리

/delete/legality는 동사형 경로입니다. DELETE /legality로 단순화하는 것이 RESTful합니다. (반환 타입은 String 유지 가능하나, 가능하면 표준화된 payload를 쓰길 권장)

적용 제안(diff):

-      @DeleteMapping("/delete/legality")
+      @DeleteMapping("/legality")
       public ResponseEntity<ApiResponse<String>> deleteOwnerLegality(

103-111: Swagger 문서화 어노테이션 누락

가이드라인에 따라 컨트롤러 엔드포인트에 Swagger(OpenAPI) 어노테이션을 추가하세요. 최소한 @Operation(summary=..., description=...)를 각 메서드에 부여하십시오.

예시(추가 코드):

import io.swagger.v3.oas.annotations.Operation;

@Operation(summary = "계약 조회", description = "특약 저장 이후 최신 계약 정보를 조회합니다.")
@GetMapping("/contract")
public ResponseEntity<ApiResponse<ContractDTO>> getContracts(...) { ... }

@Operation(summary = "특약 저장", description = "현재 계약의 특약 사항을 MongoDB에 저장합니다.")
@PostMapping("/specialContract")
public ResponseEntity<ApiResponse<Void>> saveSpecialContract(...) { ... }

@Operation(summary = "임대인 적법성 수정 요청 삭제", description = "임대인이 보낸 적법성 수정 요청을 삭제합니다.")
@DeleteMapping("/legality")
public ResponseEntity<ApiResponse<String>> deleteOwnerLegality(...) { ... }

// 나머지 엔드포인트에도 동일 패턴으로 추가

Also applies to: 112-120, 132-139, 141-150, 152-161, 163-171

src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (2)

48-49: ObjectMapper 필드 주입: 일관되게 재사용하도록 리팩터 필요

클래스 필드에 objectMapper가 추가되었지만, 메서드 내부에서 새로운 ObjectMapper를 반복 생성하는 구간이 남아 있습니다(예: Line 266-267, 316-317). 필드 인스턴스를 재사용하도록 정리하세요.

적용 제안(변경이 필요한 외부 라인 참고):

// 예: Line 266-267
// ObjectMapper objectMapper = new ObjectMapper();
// String json = objectMapper.writeValueAsString(dto);
String json = this.objectMapper.writeValueAsString(dto);

// 예: Line 316-317
// ObjectMapper objectMapper = new ObjectMapper();
// PaymentDTO dto = objectMapper.readValue(json, PaymentDTO.class);
PaymentDTO dto = this.objectMapper.readValue(json, PaymentDTO.class);

421-447: AI 응답 로깅 레벨/내용 최소화 제안

현재 warn 레벨로 응답 바디 전체를 로그합니다. 운영 환경에서는 민감/불필요 데이터가 포함될 수 있으므로, 상태코드와 요약 필드만 debug 레벨로 기록하거나 비활성화하는 것을 권장합니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between edef9df and b4fc0b2.

📒 Files selected for processing (7)
  • src/main/java/org/scoula/domain/contract/controller/ContractController.java (1 hunks)
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java (2 hunks)
  • src/main/java/org/scoula/domain/contract/dto/ContractDTO.java (2 hunks)
  • src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java (1 hunks)
  • src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java (1 hunks)
  • src/main/java/org/scoula/domain/contract/service/ContractService.java (2 hunks)
  • src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (7 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
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/contract/controller/ContractController.java
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.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/contract/controller/ContractController.java
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.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/contract/service/ContractServiceImpl.java
🧬 Code Graph Analysis (3)
src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java (1)
src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java (1)
  • Getter (5-14)
src/main/java/org/scoula/domain/contract/dto/LegalityRequestDTO.java (1)
src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java (1)
  • Getter (5-12)
src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (1)
src/main/java/org/scoula/domain/precontract/service/OwnerPreContractServiceImpl.java (1)
  • Service (50-752)
⏰ 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 (6)
src/main/java/org/scoula/domain/contract/dto/UpdateLegalityDTO.java (1)

1-12: DTO 클래스가 적절하게 구성되었습니다

Lombok 어노테이션을 활용하여 보일러플레이트 코드를 효과적으로 줄였으며, 적법성 업데이트에 필요한 필드를 간결하게 정의했습니다.

src/main/java/org/scoula/domain/contract/dto/ContractDTO.java (1)

72-72: contractChatId 소스 변경 검증 완료 — 문제 없음

ownerVO.getContractId()는 identity_verification.contract_id에 매핑되며, 매퍼(삽입/조회)에서 contractChatId로 전달/조회되므로 document.getContractChatId()와 동일한 값입니다.

  • src/main/java/org/scoula/domain/contract/dto/ContractDTO.java: .contractChatId(ownerVO.getContractId())
  • src/main/java/org/scoula/domain/precontract/vo/IdentityVerificationInfoVO.java: private Long contractId
  • src/main/resources/org/scoula/domain/precontract/mapper/OwnerPreContractMapper.xml: insertIdentityVerification에서 contract_id에 #{contractChatId} 삽입, selectIdentityVerificationInfo에서 contract_id를 조회
  • src/main/java/org/scoula/domain/contract/document/ContractMongoDocument.java: private Long contractChatId 필드 정의
src/main/java/org/scoula/domain/contract/controller/ContractController.java (1)

15-15: 구현체 확인 — ContractControllerImpl.java가 존재합니다.

ContractController 인터페이스의 구현체가 저장소에서 확인되었습니다. 구현체 파일 경로:

  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (3)

115-123: 계약 조회 시 실명인증 정보(소유자/임차인) 통합 주입 LGTM

IdentityVerificationInfoVO를 통해 계약 DTO 구성 시 주소/연락처 등 신원확인 정보를 병합하는 변경은 의도와 일치하며 문제없습니다.


382-393: 특약 저장 로직 LGTM

예외 래핑과 로깅 처리 적절합니다. 반환을 Void로 통일한 것도 컨트롤러 ApiResponse와 일치합니다.


605-616: 작성 완료 알림 LGTM

검증 호출과 사용자 알림 흐름이 간단하고 일관적입니다.

Comment on lines 15 to 116
public interface ContractController {

// step 1 (init)
@ApiOperation(value = "[계약전_임차인] 계약서를 몽고DB에 저장", notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기")
// 대기
@ApiOperation(
value = "[대기] 계약전_임차인 | 계약서를 몽고DB에 저장",
notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기")
ResponseEntity<ApiResponse<Void>> saveContractMongo(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

// step 1 : start
@ApiOperation(value = "[계약서 _ 정보 조회 1] 계약서 전체 조회", notes = "계약서 가져오기")
// 정보 조회
@ApiOperation(value = "[정보 조회] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[채팅 _ 정보 조회 1] 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message")
@ApiOperation(value = "[정보 조회] 채팅 1 | 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message")
ResponseEntity<ApiResponse<Void>> getContractNext(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

// step 1 : finish
@ApiOperation(
value = "[채팅 _ 정보 조회 2] 정보 조회에서 다음단계로 가기",
value = "[정보 조회] 채팅 2 | 정보 조회에서 다음단계로 가기",
notes = "다음 단계 여부(true/false)를 받아서 다음 단계로 넘어가기")
ResponseEntity<ApiResponse<Boolean>> nextStep(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody NextStepDTO dto);

@ApiOperation(value = "[채팅 _ 금액 조회 1]", notes = "금액을 조율하기 위해 금액을 조회")
// 금액 조정
@ApiOperation(value = "[금액 조정] 채팅 1 | 금액 조회", notes = "금액을 조율하기 위해 금액을 조회")
ResponseEntity<ApiResponse<PaymentDTO>> getDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[채팅 _ 금액 요청 2]", notes = "임대인이 금액을 요청")
@ApiOperation(value = "[금액 조정] 채팅 2 | 금액 요청 ", notes = "임대인이 금액을 요청")
ResponseEntity<ApiResponse<Void>> saveDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody PaymentDTO dto);

@ApiOperation(value = "[채팅 _ 금액 거절 3]", notes = "임차인이 금액을 거절")
@ApiOperation(value = "[금액 조정] 채팅 3 | 금액 거절", notes = "임차인이 금액을 거절")
ResponseEntity<ApiResponse<Void>> deleteDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[채팅 _ 금액 수락 4]", notes = "임대인과 임차인 모두 동의")
@ApiOperation(value = "[금액 조정] 채팅 4 | 금액 수락", notes = "임대인과 임차인 모두 동의")
ResponseEntity<ApiResponse<Void>> updateDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "AI 적법성 확인 from 몽고DB", notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기")
ResponseEntity<ApiResponse<LegalityDTO>> getLegality(
// 적법성 검사
@ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "특약을 계약서 DB에 저장 FROM 몽고DB", notes = "특약 테이블에 있는걸 계약서로 가져오기")
@ApiOperation(value = "[적법성 검사] 계약서 1 몽고DB만 있는 것 ", notes = "몽고DB에 특약 저장하기 ")
ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@ApiOperation(
value = "[적법성 검사] 채팅 1 | AI 적법성 확인 from 몽고DB ",
notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기")
ResponseEntity<ApiResponse<LegalityDTO>> getLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[적법성 검사] 채팅 2 | 임대인 삭제", notes = "임대인 : 적법성 검사 삭제")
ResponseEntity<ApiResponse<String>> deleteOwnerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[적법성 검사] 채팅 3 | 임대인 수정", notes = "임대인 : 적법성 검사 수정")
ResponseEntity<ApiResponse<Void>> updateOwnerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody UpdateLegalityDTO dto);

@ApiOperation(value = "[적법성 검사] 채팅 4 | 임차인 수정", notes = "임차인 : 적법성 검사 수정 완료")
ResponseEntity<ApiResponse<Void>> updateBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);

@ApiOperation(value = "적법성 검사 후 다음단계로 넘어가기", notes = "적법성 검사 후 AI 메세지를 보낸다")
@ApiOperation(value = "[적법성 검사] 채팅 5 | 임차인 거절", notes = "임차인 : 적법성 검사 거절")
ResponseEntity<ApiResponse<String>> rejectBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
ResponseEntity<ApiResponse<Void>> sendStep4(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

// 메서드만 만들어서 써도 될 듯
@ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Swagger 어노테이션 확인 필요

코딩 가이드라인에 따르면 모든 컨트롤러 엔드포인트에 Swagger 어노테이션을 추가해야 합니다. 현재 @ApiOperation은 있지만 파라미터에 대한 @ApiParam 어노테이션이 누락되어 있습니다.

예시로 하나의 메서드에 대한 수정 방안입니다:

+import io.swagger.annotations.ApiParam;

       ResponseEntity<ApiResponse<Void>> updateOwnerLegality(
-              @PathVariable Long contractChatId,
+              @ApiParam(value = "채팅방 ID", required = true) @PathVariable Long contractChatId,
               @AuthenticationPrincipal CustomUserDetails userDetails,
-              @RequestBody UpdateLegalityDTO dto);
+              @ApiParam(value = "적법성 업데이트 정보", required = true) @RequestBody UpdateLegalityDTO dto);

모든 메서드의 파라미터에 동일하게 적용하시기 바랍니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public interface ContractController {
// step 1 (init)
@ApiOperation(value = "[계약전_임차인] 계약서를 몽고DB에 저장", notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기")
// 대기
@ApiOperation(
value = "[대기] 계약전_임차인 | 계약서를 몽고DB에 저장",
notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기")
ResponseEntity<ApiResponse<Void>> saveContractMongo(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// step 1 : start
@ApiOperation(value = "[계약서 _ 정보 조회 1] 계약서 전체 조회", notes = "계약서 가져오기")
// 정보 조회
@ApiOperation(value = "[정보 조회] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[채팅 _ 정보 조회 1] 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message")
@ApiOperation(value = "[정보 조회] 채팅 1 | 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message")
ResponseEntity<ApiResponse<Void>> getContractNext(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// step 1 : finish
@ApiOperation(
value = "[채팅 _ 정보 조회 2] 정보 조회에서 다음단계로 가기",
value = "[정보 조회] 채팅 2 | 정보 조회에서 다음단계로 가기",
notes = "다음 단계 여부(true/false)를 받아서 다음 단계로 넘어가기")
ResponseEntity<ApiResponse<Boolean>> nextStep(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody NextStepDTO dto);
@ApiOperation(value = "[채팅 _ 금액 조회 1]", notes = "금액을 조율하기 위해 금액을 조회")
// 금액 조정
@ApiOperation(value = "[금액 조정] 채팅 1 | 금액 조회", notes = "금액을 조율하기 위해 금액을 조회")
ResponseEntity<ApiResponse<PaymentDTO>> getDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[채팅 _ 금액 요청 2]", notes = "임대인이 금액을 요청")
@ApiOperation(value = "[금액 조정] 채팅 2 | 금액 요청 ", notes = "임대인이 금액을 요청")
ResponseEntity<ApiResponse<Void>> saveDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody PaymentDTO dto);
@ApiOperation(value = "[채팅 _ 금액 거절 3]", notes = "임차인이 금액을 거절")
@ApiOperation(value = "[금액 조정] 채팅 3 | 금액 거절", notes = "임차인이 금액을 거절")
ResponseEntity<ApiResponse<Void>> deleteDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[채팅 _ 금액 수락 4]", notes = "임대인과 임차인 모두 동의")
@ApiOperation(value = "[금액 조정] 채팅 4 | 금액 수락", notes = "임대인과 임차인 모두 동의")
ResponseEntity<ApiResponse<Void>> updateDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "AI 적법성 확인 from 몽고DB", notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기")
ResponseEntity<ApiResponse<LegalityDTO>> getLegality(
// 적법성 검사
@ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "특약을 계약서 DB에 저장 FROM 몽고DB", notes = "특약 테이블에 있는걸 계약서로 가져오기")
@ApiOperation(value = "[적법성 검사] 계약서 1 몽고DB만 있는 것 ", notes = "몽고DB에 특약 저장하기 ")
ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@ApiOperation(
value = "[적법성 검사] 채팅 1 | AI 적법성 확인 from 몽고DB ",
notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기")
ResponseEntity<ApiResponse<LegalityDTO>> getLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 채팅 2 | 임대인 삭제", notes = "임대인 : 적법성 검사 삭제")
ResponseEntity<ApiResponse<String>> deleteOwnerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 채팅 3 | 임대인 수정", notes = "임대인 : 적법성 검사 수정")
ResponseEntity<ApiResponse<Void>> updateOwnerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody UpdateLegalityDTO dto);
@ApiOperation(value = "[적법성 검사] 채팅 4 | 임차인 수정", notes = "임차인 : 적법성 검사 수정 완료")
ResponseEntity<ApiResponse<Void>> updateBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);
@ApiOperation(value = "적법성 검사 후 다음단계로 넘어가기", notes = "적법성 검사 후 AI 메세지를 보낸다")
@ApiOperation(value = "[적법성 검사] 채팅 5 | 임차인 거절", notes = "임차인 : 적법성 검사 거절")
ResponseEntity<ApiResponse<String>> rejectBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
ResponseEntity<ApiResponse<Void>> sendStep4(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// 메서드만 만들어서 써도 될 듯
@ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);
public interface ContractController {
// 대기
@ApiOperation(
value = "[대기] 계약전_임차인 | 계약서를 몽고DB에 저장",
notes = "계약서에 필요한 항목들을 가져와서 몽고 DB에 계약서 만들기")
ResponseEntity<ApiResponse<Void>> saveContractMongo(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// 정보 조회
@ApiOperation(value = "[정보 조회] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[정보 조회] 채팅 1 | 정보 조회 시작", notes = "정보조회 마지막 단계에서 다음 단계로 넘어가기 Message")
ResponseEntity<ApiResponse<Void>> getContractNext(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(
value = "[정보 조회] 채팅 2 | 정보 조회에서 다음단계로 가기",
notes = "다음 단계 여부(true/false)를 받아서 다음 단계로 넘어가기")
ResponseEntity<ApiResponse<Boolean>> nextStep(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody NextStepDTO dto);
// 금액 조정
@ApiOperation(value = "[금액 조정] 채팅 1 | 금액 조회", notes = "금액을 조율하기 위해 금액을 조회")
ResponseEntity<ApiResponse<PaymentDTO>> getDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[금액 조정] 채팅 2 | 금액 요청 ", notes = "임대인이 금액을 요청")
ResponseEntity<ApiResponse<Void>> saveDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody PaymentDTO dto);
@ApiOperation(value = "[금액 조정] 채팅 3 | 금액 거절", notes = "임차인이 금액을 거절")
ResponseEntity<ApiResponse<Void>> deleteDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[금액 조정] 채팅 4 | 금액 수락", notes = "임대인과 임차인 모두 동의")
ResponseEntity<ApiResponse<Void>> updateDepositPrice(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// 적법성 검사
@ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 계약서 1 몽고DB만 있는 것 ", notes = "몽고DB에 특약 저장하기 ")
ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(
value = "[적법성 검사] 채팅 1 | AI 적법성 확인 from 몽고DB ",
notes = "몽고DB에 있는 계약서를 AI로 보내고, 적법성 받기")
ResponseEntity<ApiResponse<LegalityDTO>> getLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 채팅 2 | 임대인 삭제", notes = "임대인 : 적법성 검사 삭제")
ResponseEntity<ApiResponse<String>> deleteOwnerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 채팅 3 | 임대인 수정", notes = "임대인 : 적법성 검사 수정")
ResponseEntity<ApiResponse<Void>> updateOwnerLegality(
@io.swagger.annotations.ApiParam(value = "채팅방 ID", required = true) @PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@io.swagger.annotations.ApiParam(value = "적법성 업데이트 정보", required = true) @RequestBody UpdateLegalityDTO dto);
@ApiOperation(value = "[적법성 검사] 채팅 4 | 임차인 수정", notes = "임차인 : 적법성 검사 수정 완료")
ResponseEntity<ApiResponse<Void>> updateBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);
@ApiOperation(value = "[적법성 검사] 채팅 5 | 임차인 거절", notes = "임차인 : 적법성 검사 거절")
ResponseEntity<ApiResponse<String>> rejectBuyerLegality(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
ResponseEntity<ApiResponse<Void>> sendStep4(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
// 메서드만 만들어서 써도 될 듯
@ApiOperation(value = "바뀐 특약 수정 from 몽고DB", notes = "적법성 검사 후 수정된 특약으로 변경")
ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/controller/ContractController.java
around lines 15 to 116, add missing @ApiParam annotations for all controller
method parameters: annotate the @PathVariable Long contractChatId with
@ApiParam(value="contractChatId", required=true, example="123"), mark the
@AuthenticationPrincipal CustomUserDetails userDetails as @ApiParam(hidden=true)
(or equivalent to hide from docs), and add @ApiParam(value="request body",
required=true) to each @RequestBody DTO parameter; import
io.swagger.annotations.ApiParam and apply consistently to every method signature
in this file.

Comment on lines 67 to 70
@ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

모호한 API 설명 수정 필요

@ApiOperation의 value가 "??? 몽고 디비랑 합친것"으로 시작하는데, 이는 명확하지 않은 설명입니다. API 문서화를 위해 명확한 설명으로 수정이 필요합니다.

-      @ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
+      @ApiOperation(value = "[적법성 검사] 계약서 1 | 계약서 전체 조회 (신원확인 정보 포함)", notes = "MongoDB 계약서와 신원확인 정보를 통합하여 조회")
📝 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
@ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
@ApiOperation(value = "[적법성 검사] 계약서 1 | 계약서 전체 조회 (신원확인 정보 포함)", notes = "MongoDB 계약서와 신원확인 정보를 통합하여 조회")
ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/controller/ContractController.java
around lines 67 to 70, the @ApiOperation value is unclear ("??? 몽고 디비랑 합친것 ...")
and should be replaced with a concise, meaningful description; change the value
to a clear English or Korean phrase that describes this endpoint (e.g.,
"Retrieve contract(s) by contract chat ID" or the Korean equivalent) and update
the notes or add more detail if needed so API docs accurately reflect that this
returns a single contract or all contracts for a given contractChatId and
requires authentication.

@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

오타 수정 필요

@ApiOperation value에 "적법성 검사}" 형태로 괄호가 일치하지 않습니다.

-      @ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
+      @ApiOperation(value = "[적법성 검사] 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
📝 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
@ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
@ApiOperation(value = "[적법성 검사] 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/controller/ContractController.java
around line 106, the @ApiOperation value contains a mismatched bracket "[적법성
검사}" — replace the closing curly brace with a closing square bracket so the
string becomes "[적법성 검사]" (or otherwise ensure the opening and closing bracket
types match) and keep the rest of the annotation text unchanged.

Comment on lines 103 to 111
@Override
@PostMapping("/getContracts")
public ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(service.getContract(contractChatId, userDetails.getUserId())));
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

getContracts 엔드포인트가 잘못된 서비스 메서드를 호출합니다

/getContracts는 서비스의 getContracts(...)를 호출해야 하는데, 현재 getContract(...)를 호출하고 있습니다. 즉시 수정 필요합니다.

적용 제안(diff):

-      @PostMapping("/getContracts")
+      @PostMapping("/getContracts")
       public ResponseEntity<ApiResponse<ContractDTO>> getContracts(
               @PathVariable Long contractChatId,
               @AuthenticationPrincipal CustomUserDetails userDetails) {
           return ResponseEntity.ok(
-                  ApiResponse.success(service.getContract(contractChatId, userDetails.getUserId())));
+                  ApiResponse.success(service.getContracts(contractChatId, userDetails.getUserId())));
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
@PostMapping("/getContracts")
public ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(service.getContract(contractChatId, userDetails.getUserId())));
}
@Override
@PostMapping("/getContracts")
public ResponseEntity<ApiResponse<ContractDTO>> getContracts(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(service.getContracts(contractChatId, userDetails.getUserId())));
}
🤖 Prompt for AI Agents
In
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
around lines 103 to 111, the POST /getContracts handler is calling the wrong
service method (service.getContract) — update the call to
service.getContracts(contractChatId, userDetails.getUserId()) so the controller
invokes the plural service method that matches the endpoint; keep the method
signature and return type unchanged and import/resolve any compile errors if the
service method name differs.

Comment on lines 112 to 120
@Override
@PostMapping("/legalitysss")
public ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(
service.saveSpecialContract(contractChatId, userDetails.getUserId())));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

경로 /legalitysss는 오타/임시 경로로 보입니다 — 의미있는 리소스 경로로 교체하세요

/legalitysss는 의미가 불명확하고 유지보수성을 저해합니다. 기존 특약 리소스 규칙과 맞추어 /specialContract로 POST를 노출하는 것이 일관적입니다(동일 경로에 GET/PATCH 이미 존재).

적용 제안(diff):

-      @PostMapping("/legalitysss")
+      @PostMapping("/specialContract")
       public ResponseEntity<ApiResponse<Void>> saveSpecialContract(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
@PostMapping("/legalitysss")
public ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(
service.saveSpecialContract(contractChatId, userDetails.getUserId())));
}
@Override
@PostMapping("/specialContract")
public ResponseEntity<ApiResponse<Void>> saveSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(
service.saveSpecialContract(contractChatId, userDetails.getUserId())));
}
🤖 Prompt for AI Agents
In
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
around lines 112 to 120, replace the accidental path "/legalitysss" with a
meaningful, consistent resource path used by the other endpoints (e.g.
"/specialContract") and include the contractChatId path variable in the mapping
("/specialContract/{contractChatId}") so the existing @PathVariable Long
contractChatId matches the URL; keep this as a POST mapping to align with
existing GET/PATCH on the same resource and update any related comments/JavaDoc
to reflect the new path.

Comment on lines 338 to 381

// 적법성 검사
@Override
public ContractDTO getContracts (Long contractChatId, Long userId){
// userId 검증
validateUserId(contractChatId, userId);

ContractDTO dto;

// 몽고 DB에서 특약부분을 받아서 저장한다.
try {
repository.saveSpecialContract(contractChatId);

ContractMongoDocument document = repository.getContract(contractChatId);
if (document == null) {
throw new BusinessException(ContractException.CONTRACT_GET);
}
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
Long buyerContractId = contractMapper.getBuyerId(contractChatId);
IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId);
IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId);


// 찾은 값을 Dto에 넣고 반환하기
dto = ContractDTO.toDTO(document, ownerVO, buyerVO);
} catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
}

// ContractMongoDocument document = repository.getContract(contractChatId);
// if (document == null) {
// throw new BusinessException(ContractException.CONTRACT_GET);
// }
//
// // 찾은 값을 Dto에 넣고 반환하기
// ContractDTO dto = ContractDTO.toDTO(document);

// ContractDTO dto = getContract(contractChatId, userId);

return dto;
}

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

getContracts의 부작용(특약 저장) + 중복 로직: 책임 분리 필요

getContracts가 조회 전에 repository.saveSpecialContract(...)를 호출하며, 이어서 getContract와 유사한 DTO 변환 로직을 중복 수행합니다. 조회 메서드는 부작용 없이 동작해야 하며, 공통 로직은 재사용하는 것이 바람직합니다.

리팩터링 제안(diff): 특약 저장을 유지해야 한다면 저장 후 getContract(...)를 재사용하여 중복 제거하세요. 저장이 불필요하다면 저장 호출 자체를 제거하십시오.

-      public ContractDTO getContracts (Long contractChatId, Long userId){
-          // userId 검증
-          validateUserId(contractChatId, userId);
-
-          ContractDTO dto;
-
-          // 몽고 DB에서 특약부분을 받아서 저장한다.
-          try {
-              repository.saveSpecialContract(contractChatId);
-
-              ContractMongoDocument document = repository.getContract(contractChatId);
-              if (document == null) {
-                  throw new BusinessException(ContractException.CONTRACT_GET);
-              }
-              Long ownerContractId = contractMapper.getOwnerId(contractChatId);
-              Long buyerContractId = contractMapper.getBuyerId(contractChatId);
-              IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId);
-              IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId);
-
-              // 찾은 값을 Dto에 넣고 반환하기
-              dto = ContractDTO.toDTO(document, ownerVO, buyerVO);
-          } catch (Exception e) {
-              // 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
-              log.error("특약사항 저장 실패 ❌", e);
-              throw new BusinessException(ContractException.CONTRACT_INSERT, e);
-          }
-
-          return dto;
-      }
+      public ContractDTO getContracts(Long contractChatId, Long userId) {
+          validateUserId(contractChatId, userId);
+          try {
+              // 부작용이 반드시 필요하다면 유지, 아니라면 이 줄을 제거
+              repository.saveSpecialContract(contractChatId);
+          } catch (Exception e) {
+              log.error("특약사항 저장 실패 ❌", e);
+              throw new BusinessException(ContractException.CONTRACT_INSERT, e);
+          }
+          // 기존 조회 로직 재사용
+          return getContract(contractChatId, userId);
+      }

추가로, 저장 실패 시 사용하는 에러 코드는 CONTRACT_INSERT가 맞으나, 조회 실패는 CONTRACT_GET을 사용하도록 분리되어야 합니다(현재 혼용).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 적법성 검사
@Override
public ContractDTO getContracts (Long contractChatId, Long userId){
// userId 검증
validateUserId(contractChatId, userId);
ContractDTO dto;
// 몽고 DB에서 특약부분을 받아서 저장한다.
try {
repository.saveSpecialContract(contractChatId);
ContractMongoDocument document = repository.getContract(contractChatId);
if (document == null) {
throw new BusinessException(ContractException.CONTRACT_GET);
}
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
Long buyerContractId = contractMapper.getBuyerId(contractChatId);
IdentityVerificationInfoVO ownerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, ownerContractId);
IdentityVerificationInfoVO buyerVO = identityVerificationService.getDecryptedVerificationInfo(contractChatId, buyerContractId);
// 찾은 값을 Dto에 넣고 반환하기
dto = ContractDTO.toDTO(document, ownerVO, buyerVO);
} catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
}
// ContractMongoDocument document = repository.getContract(contractChatId);
// if (document == null) {
// throw new BusinessException(ContractException.CONTRACT_GET);
// }
//
// // 찾은 값을 Dto에 넣고 반환하기
// ContractDTO dto = ContractDTO.toDTO(document);
// ContractDTO dto = getContract(contractChatId, userId);
return dto;
}
// 적법성 검사
@Override
public ContractDTO getContracts(Long contractChatId, Long userId) {
validateUserId(contractChatId, userId);
try {
// 부작용이 반드시 필요하다면 유지, 아니라면 이 줄을 제거
repository.saveSpecialContract(contractChatId);
} catch (Exception e) {
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
}
// 기존 조회 로직 재사용
return getContract(contractChatId, userId);
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 338-381, getContracts currently performs a side-effect
repository.saveSpecialContract(...) and duplicates the retrieval/DTO conversion
logic: remove the side-effect from this read method or, if the save must remain,
call the existing getContract(...) helper after save to reuse retrieval/DTO
mapping; separate error handling so save failures throw BusinessException with
ContractException.CONTRACT_INSERT and retrieval failures throw
ContractException.CONTRACT_GET, and ensure dto is always initialized from the
reused getContract call (or returned directly) to avoid duplicated code and
ambiguous exception mapping.

Comment on lines +454 to +481
// 임대인 삭제
@Override
@Transactional
public String deleteOwnerLegality(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);

Long ownerContractId = contractMapper.getOwnerId(contractChatId);

String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}

try {
stringRedisTemplate.delete(redisKey);
// contractChatService.AiMessage(contractChatId, "임대인");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}


return "임대인이 적법성 검사를 삭제했습니다.";
}

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

임대인 전용 권한 검증 누락 + 예외 타입 일관성

  • 임대인만 삭제 가능해야 하나 validateUserId로 소유자/임차인 모두 통과할 수 있습니다. validateIsOwner로 교체 필요.
  • IllegalArgumentException, RuntimeException은 글로벌 예외 처리와 일관성이 떨어집니다. BusinessException으로 통일하세요.

적용 제안(diff):

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임대인 권한 검증
+        validateIsOwner(contractChatId, userId);
...
-        if (valueDataJson == null) {
-            throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
-        }
+        if (valueDataJson == null) {
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
+        }
...
-        } catch (Exception e) {
-            log.error("수정 요청 응답 처리 실패", e);
-            throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
-        }
+        } catch (Exception e) {
+            log.error("수정 요청 응답 처리 실패", e);
+            throw new BusinessException(ContractException.CONTRACT_REDIS, e);
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 임대인 삭제
@Override
@Transactional
public String deleteOwnerLegality(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}
try {
stringRedisTemplate.delete(redisKey);
// contractChatService.AiMessage(contractChatId, "임대인");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}
return "임대인이 적법성 검사를 삭제했습니다.";
}
// 임대인 삭제
@Override
@Transactional
public String deleteOwnerLegality(Long contractChatId, Long userId) {
// 임대인 권한 검증
validateIsOwner(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
}
try {
stringRedisTemplate.delete(redisKey);
// contractChatService.AiMessage(contractChatId, "임대인");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new BusinessException(ContractException.CONTRACT_REDIS, e);
}
return "임대인이 적법성 검사를 삭제했습니다.";
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 454 to 481, the method deleteOwnerLegality currently allows both
owner and tenant due to validateUserId and throws inconsistent exception types;
replace the user check by calling validateIsOwner(contractChatId, userId) so
only the owner can proceed, and change thrown exceptions (the case when redis
key is null and the catch block) to throw a unified BusinessException with
appropriate messages (e.g., "대기중인 수정 요청이 없습니다." and "응답 처리 중 오류가 발생했습니다.") and
include the original exception as the cause in the catch branch for debugging.

Comment on lines +483 to 517
@Override
@Transactional
public Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO updateLegalityDTO) {

// userId 검증
validateUserId(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);

// 몽고 DB에서 특약부분을 받아서 저장한다.
String redisKey = "final-contract:legality:" + contractChatId + ":" + ownerContractId;

String existingRequest = stringRedisTemplate.opsForValue().get(redisKey);
if (existingRequest != null) {
throw new IllegalArgumentException("해당 조항에 대한 수정 요청이 이미 대기중입니다.");
}

LegalityRequestDTO requestData =
LegalityRequestDTO.builder()
.legalBasis(updateLegalityDTO.getLegalBasis())
.requestId(userId)
.createdAt(LocalDateTime.now().toString())
.build();
try {
repository.saveSpecialContract(contractChatId);
} catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
String jsonData = objectMapper.writeValueAsString(requestData);
// Store as valid JSON for correct parsing later
String valueData = String.format("{\"requestData\":%s}", jsonData);
stringRedisTemplate.opsForValue().set(redisKey, valueData);

contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사 수정을 요청합니다.");
} catch (Exception e) {
log.error("수정 요청 저장 실패", e);
throw new RuntimeException("수정 요청 저장 중 오류가 발생했습니다.");
}

return null;
}
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

임대인 수정 요청: 권한 검증/중복 방지/키 보존기간 설정

  • 임대인 권한만 허용해야 합니다(validateIsOwner).
  • 중복 요청 예외도 BusinessException으로 통일.
  • Redis 키에 TTL을 두어 방치 데이터 최소화(이미 java.time.Duration import 존재).

적용 제안(diff):

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임대인 권한 검증
+        validateIsOwner(contractChatId, userId);
...
-        if (existingRequest != null) {
-            throw new IllegalArgumentException("해당 조항에 대한 수정 요청이 이미 대기중입니다.");
-        }
+        if (existingRequest != null) {
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "해당 조항에 대한 수정 요청이 이미 대기중입니다.");
+        }
...
-              stringRedisTemplate.opsForValue().set(redisKey, valueData);
+              // 48시간 TTL 부여
+              stringRedisTemplate.opsForValue().set(redisKey, valueData, Duration.ofHours(48));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
@Transactional
public Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO updateLegalityDTO) {
// userId 검증
validateUserId(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
// 몽고 DB에서 특약부분을 받아서 저장한다.
String redisKey = "final-contract:legality:" + contractChatId + ":" + ownerContractId;
String existingRequest = stringRedisTemplate.opsForValue().get(redisKey);
if (existingRequest != null) {
throw new IllegalArgumentException("해당 조항에 대한 수정 요청이 이미 대기중입니다.");
}
LegalityRequestDTO requestData =
LegalityRequestDTO.builder()
.legalBasis(updateLegalityDTO.getLegalBasis())
.requestId(userId)
.createdAt(LocalDateTime.now().toString())
.build();
try {
repository.saveSpecialContract(contractChatId);
} catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
String jsonData = objectMapper.writeValueAsString(requestData);
// Store as valid JSON for correct parsing later
String valueData = String.format("{\"requestData\":%s}", jsonData);
stringRedisTemplate.opsForValue().set(redisKey, valueData);
contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사 수정을 요청합니다.");
} catch (Exception e) {
log.error("수정 요청 저장 실패", e);
throw new RuntimeException("수정 요청 저장 중 오류가 발생했습니다.");
}
return null;
}
@Override
@Transactional
public Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO updateLegalityDTO) {
// 임대인 권한 검증
validateIsOwner(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
String redisKey = "final-contract:legality:" + contractChatId + ":" + ownerContractId;
String existingRequest = stringRedisTemplate.opsForValue().get(redisKey);
if (existingRequest != null) {
throw new BusinessException(ContractException.CONTRACT_REDIS, "해당 조항에 대한 수정 요청이 이미 대기중입니다.");
}
LegalityRequestDTO requestData =
LegalityRequestDTO.builder()
.legalBasis(updateLegalityDTO.getLegalBasis())
.requestId(userId)
.createdAt(LocalDateTime.now().toString())
.build();
try {
String jsonData = objectMapper.writeValueAsString(requestData);
// Store as valid JSON for correct parsing later
String valueData = String.format("{\"requestData\":%s}", jsonData);
// 48시간 TTL 부여
stringRedisTemplate.opsForValue().set(redisKey, valueData, Duration.ofHours(48));
contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사 수정을 요청합니다.");
} catch (Exception e) {
log.error("수정 요청 저장 실패", e);
throw new RuntimeException("수정 요청 저장 중 오류가 발생했습니다.");
}
return null;
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 483 to 517, enforce owner-only access, unify duplicate-request
errors to BusinessException, and add a TTL when setting the Redis key: call
validateIsOwner(contractChatId, userId) (instead of only validateUserId) before
creating the request; replace the IllegalArgumentException thrown on
existingRequest != null with a BusinessException carrying the appropriate error
code/message; when storing the key use the opsForValue().set(redisKey,
valueData, Duration.ofMinutes(30)) (or a configurable Duration) so the Redis
entry expires; keep the current JSON wrapping and existing try/catch behavior,
but ensure the BusinessException import is used and adjust any tests or callers
accordingly.

Comment on lines +519 to +560
// 임차인 수정
@Override
@Transactional
public Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto) {

// userId 검증
validateUserId(contractChatId, userId);

Long ownerContractId = contractMapper.getOwnerId(contractChatId);

String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}

try{
// JSON에서 clauseOrder와 requestData 추출
com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(valueDataJson);
String requestDataJson = rootNode.get("requestData").toString();

LegalityRequestDTO requestData =
objectMapper.readValue(requestDataJson, LegalityRequestDTO.class);

// 수정하는 로직 짜기
updateSpecialContract(contractChatId, userId, dto);
String resultMessage;
resultMessage =
String.format("적법성 수정 후 레디스에서 삭제되었습니다.");

stringRedisTemplate.delete(redisKey);
contractChatService.AiMessage(contractChatId, resultMessage);

}catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}


return null;
}
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

임차인 전용 권한 검증 누락 + 불필요한 JSON 파싱

  • 임차인만 응답 가능해야 합니다. 현재 validateUserId는 임대인도 통과합니다. validateIsBuyer(신규)로 교체하세요.
  • Redis에서 requestData를 파싱하고 있지만 결과를 사용하지 않습니다. 불필요한 파싱은 제거 가능.

적용 제안(diff):

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임차인 권한 검증
+        validateIsBuyer(contractChatId, userId);
...
-            // JSON에서 clauseOrder와 requestData 추출
-            com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(valueDataJson);
-            String requestDataJson = rootNode.get("requestData").toString();
-
-            LegalityRequestDTO requestData =
-                    objectMapper.readValue(requestDataJson, LegalityRequestDTO.class);
+            // (옵션) 필요한 경우에만 requestData 파싱

신규 메서드(클래스 내부에 추가 필요):

// 임차인 권한 검증
private void validateIsBuyer(Long contractChatId, Long userId) {
    Long buyerId = tenantMapper
            .selectContractBuyerId(contractChatId)
            .orElseThrow(() -> new BusinessException(PreContractErrorCode.TENANT_USER));
    if (!userId.equals(buyerId)) {
        throw new BusinessException(PreContractErrorCode.TENANT_USER);
    }
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 519 to 560, replace the broad validateUserId check with a
buyer-only check and remove the unused JSON parsing: call a new private
validateIsBuyer(contractChatId, userId) (implement it in this class using
tenantMapper.selectContractBuyerId(...).orElseThrow(() -> new
BusinessException(PreContractErrorCode.TENANT_USER)) and compare IDs, throwing
BusinessException if not equal), delete the code that reads/parses requestData
from Redis (remove objectMapper.readTree(...) and objectMapper.readValue(...)
and the unused requestData/requestDataJson variables), and keep the Redis
existence check, the updateSpecialContract call, deletion of the Redis key, and
the AiMessage call inside the try/catch; ensure exceptions still log and rethrow
appropriately.

Comment on lines 562 to 588
// 임차인 거절
@Override
@Transactional
public String rejectBuyerLegality(Long contractChatId, Long userId) {

// userId 검증
validateUserId(contractChatId, userId);

Long ownerContractId = contractMapper.getOwnerId(contractChatId);

String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}

try {
// contractChatService.AiMessage(contractChatId, "임대인이 적법성 수정을 거절했습니다.");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}

return "임대인이 적법성 수정을 거절했습니다.";
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

거절 처리: 권한 검증/키 삭제/메시지 문구 오류

  • 임차인만 거절할 수 있어야 합니다. validateIsBuyer 사용.
  • 거절 시 Redis 요청을 삭제하지 않아 대기 상태가 지속됩니다. 삭제 필요.
  • 반환/안내 문구가 “임대인”으로 되어 있는데, 메서드 의미상 “임차인”이어야 합니다.

적용 제안(diff):

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임차인 권한 검증
+        validateIsBuyer(contractChatId, userId);
...
-        if (valueDataJson == null) {
-            throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
-        }
+        if (valueDataJson == null) {
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
+        }
...
-        try {
-//            contractChatService.AiMessage(contractChatId, "임대인이 적법성 수정을 거절했습니다.");
-        } catch (Exception e) {
-            log.error("수정 요청 응답 처리 실패", e);
-            throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
-        }
-
-        return "임대인이 적법성 수정을 거절했습니다.";
+        try {
+            stringRedisTemplate.delete(redisKey);
+            contractChatService.AiMessage(contractChatId, "임차인이 적법성 수정을 거절했습니다.");
+        } catch (Exception e) {
+            log.error("수정 요청 응답 처리 실패", e);
+            throw new BusinessException(ContractException.CONTRACT_REDIS, e);
+        }
+
+        return "임차인이 적법성 수정을 거절했습니다.";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 임차인 거절
@Override
@Transactional
public String rejectBuyerLegality(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}
try {
// contractChatService.AiMessage(contractChatId, "임대인이 적법성 수정을 거절했습니다.");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}
return "임대인이 적법성 수정을 거절했습니다.";
}
// 임차인 거절
@Override
@Transactional
public String rejectBuyerLegality(Long contractChatId, Long userId) {
// 임차인 권한 검증
validateIsBuyer(contractChatId, userId);
Long ownerContractId = contractMapper.getOwnerId(contractChatId);
String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
}
try {
stringRedisTemplate.delete(redisKey);
contractChatService.AiMessage(contractChatId, "임차인이 적법성 수정을 거절했습니다.");
} catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new BusinessException(ContractException.CONTRACT_REDIS, e);
}
return "임차인이 적법성 수정을 거절했습니다.";
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 562 to 588, the rejectBuyerLegality method currently lacks
buyer-only authorization, does not remove the Redis pending request, and returns
the wrong actor in its message; update the method to: call
validateIsBuyer(contractChatId, userId) instead of or in addition to
validateUserId to ensure only the tenant can reject, delete the Redis key
(stringRedisTemplate.delete(redisKey)) after handling the rejection so the
pending state is cleared, and change the returned and logged message text from
“임대인” to “임차인” so it correctly states that the tenant has rejected the legality
modification.

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 (6)
src/main/java/org/scoula/domain/contract/service/ContractService.java (1)

95-136: 중복된 JavaDoc 설명 수정 필요

여러 메서드에서 동일한 JavaDoc 설명("계약서를 AI로 보내고, 적법성 받기")이 사용되고 있습니다. 각 메서드의 실제 기능에 맞게 설명을 수정해야 합니다.

       /**
-       * step4 적법성 검사 후 수정된 특약으로 변경
+       * step4 적법성 검사 후 수정된 특약 저장
        *
        * @param contractChatId 채팅방 아이디
-       * @param userId 유저 아이디 @Param dto 변경된 특약
+       * @param userId 유저 아이디
+       * @param dto 변경된 특약
        */
       Void updateSpecialContract(Long contractChatId, Long userId, SpecialContractUpdateDTO dto);

       /**
-       * step4 (init) 계약서를 AI로 보내고, 적법성 받기
+       * step4 임대인의 적법성 요청을 삭제
        *
        * @param contractChatId 채팅방 아이디
        * @param userId 유저 아이디
-       * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기
+       * @return 삭제 완료 메시지
        */
       String deleteOwnerLegality(Long contractChatId, Long userId);

       /**
-       * step4 (init) 계약서를 AI로 보내고, 적법성 받기
+       * step4 임대인의 적법성 요청을 업데이트
        *
        * @param contractChatId 채팅방 아이디
        * @param userId 유저 아이디
-       * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기
+       * @param dto 업데이트할 적법성 정보
        */
       Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO dto);

       /**
-       * step4 (init) 계약서를 AI로 보내고, 적법성 받기
+       * step4 임차인의 적법성 요청을 업데이트
        *
        * @param contractChatId 채팅방 아이디
        * @param userId 유저 아이디
-       * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기
+       * @param dto 업데이트할 특약 정보
        */
       Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto);

       /**
-       * step4 (init) 계약서를 AI로 보내고, 적법성 받기
+       * step4 임차인의 적법성 요청을 거절
        *
        * @param contractChatId 채팅방 아이디
        * @param userId 유저 아이디
-       * @return AI가 계약서를 보고 주는 적법성을 리턴값으로 보내기
+       * @return 거절 완료 메시지
        */
       String rejectBuyerLegality(Long contractChatId, Long userId);
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java (1)

165-172: GET 메서드로 상태 변경 작업 수행 - RESTful 원칙 위반

rejectBuyerLegality는 상태를 변경하는 작업인데 GET 메서드를 사용하고 있습니다. POST 또는 DELETE 메서드가 더 적절합니다.

       @Override
-      @GetMapping("/reject/legality")
+      @PostMapping("/reject/legality")
       public ResponseEntity<ApiResponse<String>> rejectBuyerLegality(
               @PathVariable Long contractChatId,
               @AuthenticationPrincipal CustomUserDetails userDetails) {
           return ResponseEntity.ok(
                   ApiResponse.success(
                           service.rejectBuyerLegality(contractChatId, userDetails.getUserId())));
       }
src/main/java/org/scoula/domain/contract/controller/ContractController.java (1)

106-106: 괄호 불일치 오타 수정 필요

@ApiOperation value에 "[적법성 검사}" 형태로 괄호가 일치하지 않습니다.

-      @ApiOperation(value = "[적법성 검사} 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
+      @ApiOperation(value = "[적법성 검사] 채팅 6 | 적법성 검사 후 다음단계로 넘어가기", notes = "내보내기 단계로 넘어가기")
src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (3)

565-588: 임차인 권한 검증 및 메시지 텍스트 수정 필요

거절은 임차인만 할 수 있어야 하며, 반환 메시지가 "임대인"이 아닌 "임차인"이어야 합니다.

     @Override
     @Transactional
     public String rejectBuyerLegality(Long contractChatId, Long userId) {

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임차인 권한 검증
+        validateIsBuyer(contractChatId, userId);

         Long ownerContractId = contractMapper.getOwnerId(contractChatId);

         String redisKey =
                 "final-contract:legality:" + contractChatId + ":" + ownerContractId;
         String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
         if (valueDataJson == null) {
-            throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
         }

         try {
             stringRedisTemplate.delete(redisKey);
-//            contractChatService.AiMessage(contractChatId, "임대인이 적법성 수정을 거절했습니다.");
+            contractChatService.AiMessage(contractChatId, "임차인이 적법성 수정을 거절했습니다.");
         } catch (Exception e) {
             log.error("수정 요청 응답 처리 실패", e);
-            throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, e);
         }

-        return "임대인이 적법성 수정을 거절했습니다.";
+        return "임차인이 적법성 수정을 거절했습니다.";
     }

485-517: 임대인 권한 검증 및 Redis TTL 설정 필요

임대인만 수정 요청을 할 수 있도록 권한을 강화하고, Redis 키에 TTL을 설정하여 오래된 데이터가 남지 않도록 해야 합니다.

     @Override
     @Transactional
     public Void updateOwnerLegality(Long contractChatId, Long userId, UpdateLegalityDTO updateLegalityDTO) {

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임대인 권한 검증
+        validateIsOwner(contractChatId, userId);
         Long ownerContractId = contractMapper.getOwnerId(contractChatId);

         String redisKey = "final-contract:legality:" + contractChatId + ":" + ownerContractId;

         String existingRequest = stringRedisTemplate.opsForValue().get(redisKey);
         if (existingRequest != null) {
-            throw new IllegalArgumentException("해당 조항에 대한 수정 요청이 이미 대기중입니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "해당 조항에 대한 수정 요청이 이미 대기중입니다.");
         }

         LegalityRequestDTO requestData =
                 LegalityRequestDTO.builder()
                         .legalBasis(updateLegalityDTO.getLegalBasis())
                         .requestId(userId)
                         .createdAt(LocalDateTime.now().toString())
                         .build();
         try {
             String jsonData = objectMapper.writeValueAsString(requestData);
             // Store as valid JSON for correct parsing later
             String valueData = String.format("{\"requestData\":%s}", jsonData);
-            stringRedisTemplate.opsForValue().set(redisKey, valueData);
+            // 48시간 TTL 설정
+            stringRedisTemplate.opsForValue().set(redisKey, valueData, Duration.ofHours(48));

             contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사 수정을 요청합니다.");
         }  catch (Exception e) {
             log.error("수정 요청 저장 실패", e);
-            throw new RuntimeException("수정 요청 저장 중 오류가 발생했습니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, e);
         }

         return null;
     }

457-480: 임대인 권한 검증 및 예외 처리 일관성 개선 필요

deleteOwnerLegality에서 임대인만 삭제할 수 있도록 권한 검증을 강화하고, 예외 타입을 일관되게 사용해야 합니다.

     @Override
     @Transactional
     public String deleteOwnerLegality(Long contractChatId, Long userId) {
-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임대인 권한 검증
+        validateIsOwner(contractChatId, userId);

         Long ownerContractId = contractMapper.getOwnerId(contractChatId);

         String redisKey =
                 "final-contract:legality:" + contractChatId + ":" + ownerContractId;
         String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
         if (valueDataJson == null) {
-            throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, "대기중인 수정 요청이 없습니다.");
         }

         try {
             stringRedisTemplate.delete(redisKey);
-//            contractChatService.AiMessage(contractChatId, "임대인");
+            contractChatService.AiMessage(contractChatId, "임대인이 적법성 검사를 삭제했습니다.");
         } catch (Exception e) {
             log.error("수정 요청 응답 처리 실패", e);
-            throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
+            throw new BusinessException(ContractException.CONTRACT_REDIS, e);
         }

         return "임대인이 적법성 검사를 삭제했습니다.";
     }
🧹 Nitpick comments (4)
src/main/java/org/scoula/domain/contract/service/ContractService.java (1)

82-82: JavaDoc 형식 오류 수정 필요

JavaDoc이 한 줄로 압축되어 있어 가독성이 떨어집니다. 적절한 형식으로 수정해주세요.

-      /** * step4 start 특약을 개약 테이블에 저장하기 * * @param contractChatId 채팅방 아이디 * @param userId 유저 아이디 */
+      /**
+       * step4 start 특약을 계약 테이블에 저장하기
+       * 
+       * @param contractChatId 채팅방 아이디
+       * @param userId 유저 아이디
+       */
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java (1)

103-111: 주석 처리된 코드 제거 권장

주석 처리된 getContracts 메서드는 사용되지 않으며, 서비스 메서드 호출도 잘못되어 있습니다. 필요하지 않다면 제거하는 것이 좋습니다.

-      //      @Override
-      //      @PostMapping("/getContracts")
-      //      public ResponseEntity<ApiResponse<ContractDTO>> getContracts(
-      //              @PathVariable Long contractChatId,
-      //              @AuthenticationPrincipal CustomUserDetails userDetails) {
-      //          return ResponseEntity.ok(
-      //                  ApiResponse.success(service.getContract(contractChatId,
-      // userDetails.getUserId())));
-      //      }
src/main/java/org/scoula/domain/contract/controller/ContractController.java (1)

66-70: 주석 처리된 코드 제거 권장

사용하지 않는 주석 처리된 getContracts 메서드는 코드베이스를 깔끔하게 유지하기 위해 제거하는 것이 좋습니다.

-      //      // 적법성 검사
-      //      @ApiOperation(value = "??? 몽고 디비랑 합친것 [적법성 검사] 계약서 1 | 계약서 전체 조회", notes = "계약서 가져오기")
-      //      ResponseEntity<ApiResponse<ContractDTO>> getContracts(
-      //              @PathVariable Long contractChatId,
-      //              @AuthenticationPrincipal CustomUserDetails userDetails);
src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (1)

339-380: 사용하지 않는 주석 코드 제거

대량의 주석 처리된 getContracts 메서드가 있습니다. 사용하지 않는다면 제거하여 코드베이스를 깔끔하게 유지하세요.

주석 처리된 전체 메서드를 제거하시기 바랍니다.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b4fc0b2 and 7daa1df.

📒 Files selected for processing (4)
  • src/main/java/org/scoula/domain/contract/controller/ContractController.java (1 hunks)
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java (2 hunks)
  • src/main/java/org/scoula/domain/contract/service/ContractService.java (2 hunks)
  • src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (9 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
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/contract/controller/ContractController.java
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.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/contract/controller/ContractController.java
  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.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/contract/service/ContractServiceImpl.java
🧠 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/**/controller/**/*.java : Add Swagger annotations to controller endpoints for API documentation

Applied to files:

  • src/main/java/org/scoula/domain/contract/controller/ContractController.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/java/org/scoula/**/controller/*Controller{,Impl}.java : Use interface-implementation pattern for controllers: `*Controller.java` (interface) and `*ControllerImpl.java` (implementation)

Applied to files:

  • src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CodeQL Security Analysis (java)
  • GitHub Check: Test Coverage
🔇 Additional comments (1)
src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java (1)

196-197: 주석 처리된 코드 제거 또는 복원 필요

스텝 변경 코드가 주석 처리되어 있습니다. 의도적이라면 제거하고, 필요한 코드라면 복원해주세요.

-//            // 스텝 변경
-//            contractChatMapper.updateStatus(contractChatId, ContractChat.ContractStatus.STEP1);
+            // 스텝 변경
+            contractChatMapper.updateStatus(contractChatId, ContractChat.ContractStatus.STEP1);

Comment on lines +383 to +392
public Void saveSpecialContract(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);
// 몽고 DB에서 특약부분을 받아서 저장한다.
try { repository.saveSpecialContract(contractChatId); } catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e); }
return null;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 및 포맷팅 개선 필요

특약 저장 로직의 try-catch 블록 포맷팅이 불규칙하고, 예외 메시지가 명확하지 않습니다.

     @Override
     public Void saveSpecialContract(Long contractChatId, Long userId) {
-          // userId 검증
-         validateUserId(contractChatId, userId);
-         // 몽고 DB에서 특약부분을 받아서 저장한다.
-         try {        repository.saveSpecialContract(contractChatId);    } catch (Exception e) {
-             // 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
-              log.error("특약사항 저장 실패 ❌", e);
-              throw new BusinessException(ContractException.CONTRACT_INSERT, e);    }
-         return null;
+        // userId 검증
+        validateUserId(contractChatId, userId);
+        
+        // 몽고 DB에서 특약부분을 받아서 저장한다.
+        try {
+            repository.saveSpecialContract(contractChatId);
+        } catch (Exception e) {
+            log.error("특약사항 저장 실패 - contractChatId: {}", contractChatId, e);
+            throw new BusinessException(ContractException.CONTRACT_INSERT, e);
+        }
+        return null;
      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Void saveSpecialContract(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);
// 몽고 DB에서 특약부분을 받아서 저장한다.
try { repository.saveSpecialContract(contractChatId); } catch (Exception e) {
// 예외 로그 기록 및 사용자에게 전달할 메시지 등 처리
log.error("특약사항 저장 실패 ❌", e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e); }
return null;
}
@Override
public Void saveSpecialContract(Long contractChatId, Long userId) {
// userId 검증
validateUserId(contractChatId, userId);
// 몽고 DB에서 특약부분을 받아서 저장한다.
try {
repository.saveSpecialContract(contractChatId);
} catch (Exception e) {
log.error("특약사항 저장 실패 - contractChatId: {}", contractChatId, e);
throw new BusinessException(ContractException.CONTRACT_INSERT, e);
}
return null;
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 383 to 392, the try-catch block is poorly formatted and the
exception log/message is not descriptive; reformat the try-catch to standard
Java indentation, call repository.saveSpecialContract(contractChatId) inside the
try on its own line, and improve error reporting by logging a clear message that
includes contextual identifiers (contractChatId and userId) and the exception
(e.g., log.error("Failed to save special contract for contractChatId={},
userId={}", contractChatId, userId, e)); then throw a BusinessException
constructed with a descriptive message and the caught exception as the cause.

Comment on lines +522 to +560
public Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto) {

// userId 검증
validateUserId(contractChatId, userId);

Long ownerContractId = contractMapper.getOwnerId(contractChatId);

String redisKey =
"final-contract:legality:" + contractChatId + ":" + ownerContractId;
String valueDataJson = stringRedisTemplate.opsForValue().get(redisKey);
if (valueDataJson == null) {
throw new IllegalArgumentException("대기중인 수정 요청이 없습니다.");
}

try{
// JSON에서 clauseOrder와 requestData 추출
com.fasterxml.jackson.databind.JsonNode rootNode = objectMapper.readTree(valueDataJson);
String requestDataJson = rootNode.get("requestData").toString();

LegalityRequestDTO requestData =
objectMapper.readValue(requestDataJson, LegalityRequestDTO.class);

// 수정하는 로직 짜기
updateSpecialContract(contractChatId, userId, dto);
String resultMessage;
resultMessage =
String.format("적법성 수정 후 레디스에서 삭제되었습니다.");

stringRedisTemplate.delete(redisKey);
contractChatService.AiMessage(contractChatId, resultMessage);

}catch (Exception e) {
log.error("수정 요청 응답 처리 실패", e);
throw new RuntimeException("응답 처리 중 오류가 발생했습니다.");
}


return null;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

임차인 권한 검증 필요

임차인만 수정할 수 있도록 전용 권한 검증이 필요합니다. 현재 validateUserId는 임대인도 통과할 수 있습니다.

임차인 전용 검증 메서드 validateIsBuyer를 추가해야 합니다:

// 임차인 권한 검증
private void validateIsBuyer(Long contractChatId, Long userId) {
    Long buyerId = tenantMapper
            .selectContractBuyerId(contractChatId)
            .orElseThrow(() -> new BusinessException(PreContractErrorCode.TENANT_USER));
    if (!userId.equals(buyerId)) {
        throw new BusinessException(PreContractErrorCode.TENANT_USER);
    }
}

그리고 메서드를 다음과 같이 수정하세요:

    @Override
    @Transactional
    public Void updateBuyerLegality(Long contractChatId, Long userId, SpecialContractUpdateDTO dto) {

-        // userId 검증
-        validateUserId(contractChatId, userId);
+        // 임차인 권한 검증
+        validateIsBuyer(contractChatId, userId);

새로운 메서드를 추가하시겠습니까?

🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/contract/service/ContractServiceImpl.java
around lines 522 to 560, the method updateBuyerLegality currently uses
validateUserId which allows landlords as well; add a new private method
validateIsBuyer that fetches the buyerId via
tenantMapper.selectContractBuyerId(contractChatId) and throws
BusinessException(PreContractErrorCode.TENANT_USER) when absent or when userId
does not equal buyerId, then replace the call to validateUserId(...) at the
start of updateBuyerLegality with validateIsBuyer(...); place the new
validateIsBuyer method in the same class (private) and use the same
BusinessException and error code as in the review comment.

Copy link
Contributor

@MeongW MeongW left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!~~

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

Labels

✨ feature 새로운 기능 요청

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 03 적법성 검사

2 participants