Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.romrom.common.constant;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum TradeReviewRating {
BAD("별로예요"),
GOOD("좋아요"),
GREAT("최고예요"),
;

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.romrom.common.constant;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum TradeReviewTag {
FAST_RESPONSE("답장이 빨라요"),
GOOD_ITEM_CONDITION("물건 상태가 좋아요"),
MATCHES_PHOTO("사진과 같아요"),
PUNCTUAL("약속을 잘 지켜요"),
KIND("친절해요"),
;

private final String description;
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ public enum ErrorCode {

TRADE_COMPLETION_REQUEST_FORBIDDEN(HttpStatus.FORBIDDEN, "교환 완료 요청 처리 권한이 없습니다."),

// TRADE REVIEW

TRADE_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "거래가 완료되지 않아 후기를 작성할 수 없습니다."),

TRADE_REVIEW_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "이미 해당 거래에 후기를 작성했습니다."),

TRADE_REVIEW_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, "해당 거래의 당사자만 후기를 작성할 수 있습니다."),

// CHAT

CANNOT_SEND_MESSAGE_TO_DELETED_CHATROOM(HttpStatus.FORBIDDEN, "거래요청이 취소되었거나 거래완료된 상태이므로, 메시지를 보낼 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.romrom.common.constant.ItemTradeOption;
import com.romrom.common.constant.TradeReviewRating;
import com.romrom.common.constant.TradeReviewTag;
import com.romrom.member.entity.Member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
Expand Down Expand Up @@ -54,4 +56,15 @@ public TradeRequest() {
@Min(value = 0, message = "페이지 사이즈에 음수는 입력될 수 없습니다.")
@Max(value = Integer.MAX_VALUE, message = "정수 최대 범위를 넘을 수 없습니다.")
private Integer pageSize; // 페이지 사이즈

// 후기 작성 필드
@Schema(description = "종합 평가 (BAD/GOOD/GREAT)")
private TradeReviewRating tradeReviewRating;

@Schema(description = "세부 항목 태그 목록")
@Builder.Default
private List<TradeReviewTag> tradeReviewTags = new ArrayList<>();

@Schema(description = "한마디 (최대 200자, 선택)")
private String reviewComment;
Comment thread
nayoung04 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

import com.romrom.item.entity.postgres.Item;
import com.romrom.item.entity.postgres.TradeRequestHistory;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.*;
import org.springframework.data.domain.Page;

@ToString
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.romrom.item.entity.postgres;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.romrom.common.constant.TradeReviewRating;
import com.romrom.common.constant.TradeReviewTag;
import com.romrom.common.entity.postgres.BasePostgresEntity;
import com.romrom.member.entity.Member;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.SuperBuilder;

import java.util.List;
import java.util.UUID;

@Entity
@Getter
@Setter
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@ToString(callSuper = true)
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class TradeReview extends BasePostgresEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(nullable = false, updatable = false)
private UUID tradeReviewId;

@ManyToOne(fetch = FetchType.LAZY)
private TradeRequestHistory tradeRequestHistory;

@ManyToOne(fetch = FetchType.LAZY)
private Member reviewerMember; // 후기 작성자

@ManyToOne(fetch = FetchType.LAZY)
private Member reviewedMember; // 후기 받는 사람
Comment thread
nayoung04 marked this conversation as resolved.

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TradeReviewRating tradeReviewRating; // 종합 평가

@ElementCollection
private List<TradeReviewTag> tradeReviewTags; // 세부 항목
Comment thread
nayoung04 marked this conversation as resolved.

@Column(length = 200)
private String reviewComment; // 한마디
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.romrom.item.repository.postgres;

import com.romrom.item.entity.postgres.TradeRequestHistory;
import com.romrom.item.entity.postgres.TradeReview;
import com.romrom.member.entity.Member;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TradeReviewRepository extends JpaRepository<TradeReview, UUID> {

// 동일 거래에 같은 작성자의 후기가 이미 존재하는지 확인
boolean existsByTradeRequestHistoryAndReviewerMember(
TradeRequestHistory tradeRequestHistory, Member reviewerMember);
Comment thread
nayoung04 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.romrom.item.service;

import com.romrom.common.constant.TradeStatus;
import com.romrom.common.exception.CustomException;
import com.romrom.common.exception.ErrorCode;
import com.romrom.item.dto.TradeRequest;
import com.romrom.item.entity.postgres.TradeRequestHistory;
import com.romrom.item.entity.postgres.TradeReview;
import com.romrom.item.repository.postgres.TradeRequestHistoryRepository;
import com.romrom.item.repository.postgres.TradeReviewRepository;
import com.romrom.member.entity.Member;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class TradeReviewService {

private final TradeReviewRepository tradeReviewRepository;
private final TradeRequestHistoryRepository tradeRequestHistoryRepository;

// 후기 작성
@Transactional
public void postTradeReview(TradeRequest request) {
TradeRequestHistory tradeRequestHistory = findTradeRequestHistoryById(request.getTradeRequestHistoryId());
Member reviewerMember = request.getMember();

// 종합 평가 필수 검증
if (request.getTradeReviewRating() == null) {
log.error("종합 평가가 누락되었습니다. memberId={}", reviewerMember.getMemberId());
throw new CustomException(ErrorCode.INVALID_REQUEST);
}
Comment thread
nayoung04 marked this conversation as resolved.

// 거래 완료 상태 검증
if (tradeRequestHistory.getTradeStatus() != TradeStatus.TRADED) {
log.error("거래 완료 상태가 아닙니다. tradeRequestHistoryId={}, status={}",
tradeRequestHistory.getTradeRequestHistoryId(), tradeRequestHistory.getTradeStatus());
throw new CustomException(ErrorCode.TRADE_NOT_COMPLETED);
}

// 후기 작성 권한 검증 (거래 당사자만 작성 가능)
Member takeItemOwner = tradeRequestHistory.getTakeItem().getMember();
Member giveItemOwner = tradeRequestHistory.getGiveItem().getMember();
boolean isTradeParticipant = reviewerMember.getMemberId().equals(takeItemOwner.getMemberId())
|| reviewerMember.getMemberId().equals(giveItemOwner.getMemberId());
if (!isTradeParticipant) {
log.error("거래 당사자가 아닙니다. memberId={}, tradeRequestHistoryId={}",
reviewerMember.getMemberId(), tradeRequestHistory.getTradeRequestHistoryId());
throw new CustomException(ErrorCode.TRADE_REVIEW_ACCESS_FORBIDDEN);
}

// 중복 후기 검증
if (tradeReviewRepository.existsByTradeRequestHistoryAndReviewerMember(tradeRequestHistory, reviewerMember)) {
log.error("이미 후기를 작성했습니다. memberId={}, tradeRequestHistoryId={}",
reviewerMember.getMemberId(), tradeRequestHistory.getTradeRequestHistoryId());
throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
}

// 후기 받는 사람 결정 (작성자의 반대편 거래 참여자)
Member reviewedMember = reviewerMember.getMemberId().equals(takeItemOwner.getMemberId())
? giveItemOwner
: takeItemOwner;

TradeReview tradeReview = TradeReview.builder()
.tradeRequestHistory(tradeRequestHistory)
.reviewerMember(reviewerMember)
.reviewedMember(reviewedMember)
.tradeReviewRating(request.getTradeReviewRating())
.tradeReviewTags(request.getTradeReviewTags())
.reviewComment(request.getReviewComment())
.build();
try {
tradeReviewRepository.save(tradeReview);
} catch (DataIntegrityViolationException e) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

DataIntegrityViolationException 에 대해서만 핸들링하나요?

log.warn("중복 후기 저장 시도 (DB 유니크 제약 위반): tradeRequestHistoryId={}, memberId={}",
tradeRequestHistory.getTradeRequestHistoryId(), reviewerMember.getMemberId());
throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
}
Comment on lines +80 to +84
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] TradeReview 제약/필드 확인"
fd -i 'TradeReview.java' --exec sed -n '1,220p' {}

echo
echo "[2] 후기 코멘트 길이 및 요청 검증 로직 탐색"
rg -n --type=java -C2 'reviewComment|@Size|length = 200|tradeReviewRating|INVALID_REQUEST'

echo
echo "[3] DataIntegrityViolationException 처리 지점 및 매핑 코드 확인"
rg -n --type=java -C3 'DataIntegrityViolationException|TRADE_REVIEW_ALREADY_EXISTS'

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 26659


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Full TradeRequest.java definition"
fd -i 'TradeRequest.java' -path '*/item/dto/*' --exec cat {} \;

echo
echo "[2] Search for `@Size/`@Length/@Valid/@Validated annotations on reviewComment"
rg -n --type=java '@Size|@Length|@Validated' -B2 -A2 'reviewComment'

echo
echo "[3] Controller layer - validate if request body validation is enabled"
rg -n --type=java '@Valid|@RequestBody' --glob='*TradeController.java' -C3 'createTradeReview'

echo
echo "[4] Check for any database migration or constraint definition files"
fd -i 'migration|schema|constraint|flyway' -type f | head -20

echo
echo "[5] Verify if there are `@NotNull/`@NotBlank on other TradeReview fields"
rg -n --type=java -B5 'class TradeReview' | head -50

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Full TradeRequest.java definition"
fd 'TradeRequest.java' -path '*/item/dto/*' --exec cat {} \;

echo
echo "[2] Search for `@Size/`@Length/@Valid/@Validated annotations in DTO"
rg -n --type=java '@Size|@Length|@Valid|@NotNull|@NotBlank' RomRom-Domain-Item/src/main/java/com/romrom/item/dto/

echo
echo "[3] Controller layer validation on createTradeReview endpoint"
rg -n --type=java -A10 'createTradeReview' | grep -A10 '@Valid\|@RequestBody'

echo
echo "[4] Check TradeReview entity for all column constraints"
fd 'TradeReview.java' -path '*/entity/postgres/*' --exec cat {} \;

echo
echo "[5] Search for unique constraint definitions in entity or schema"
rg -n --type=java '@Unique|uniqueConstraints|@Column.*unique' RomRom-Domain-Item/

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Full TradeRequest.java definition"
find . -name "TradeRequest.java" -path "*/dto/*" -exec cat {} \;

echo
echo "[2] Search for validation annotations in item/dto directory"
rg '@Size|@Length|@Valid|@NotNull|@NotBlank' RomRom-Domain-Item/src/main/java/com/romrom/item/dto/ || true

echo
echo "[3] Full TradeReview entity definition"
find . -name "TradeReview.java" -path "*/entity/postgres/*" -exec cat {} \;

echo
echo "[4] Search for unique constraints in TradeReview"
rg -C5 'TradeReview|@Unique' RomRom-Domain-Item/src/main/java/com/romrom/item/entity/ || true

echo
echo "[5] Search for `@Valid` on createTradeReview controller endpoint"
rg -B5 -A15 'createTradeReview' RomRom-Web/src/main/java/com/romrom/web/controller/ || true

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 7430


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Search for Flyway/Liquibase migration files with TradeReview constraints"
find . -type f \( -name "*.sql" -o -name "*.yaml" -o -name "*.xml" \) | xargs grep -l "trade_review\|TradeReview" 2>/dev/null || echo "No migration files found with TradeReview"

echo
echo "[2] Search for `@Table` annotation with unique constraints on TradeReview"
rg '@Table' RomRom-Domain-Item/src/main/java/com/romrom/item/entity/ -A5

echo
echo "[3] Check BasePostgresEntity for any constraint definitions"
find . -name "BasePostgresEntity.java" -exec cat {} \;

echo
echo "[4] Search for TradeController createTradeReview method signature with `@Valid`"
find . -name "*TradeController.java" ! -path "*/Docs.java" -exec grep -A10 "createTradeReview" {} \;

echo
echo "[5] Look for database initialization or schema definition files in resources"
find . -path "*/resources/*" -name "*.sql" -o -name "*.yaml" | head -20

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Read TradeReview migration file"
cat ./RomRom-Web/src/main/resources/db/migration/V1_4_58__add_trade_review_constraints.sql

echo
echo "[2] Search for all migration files related to TradeReview in order"
find . -path "*/db/migration/*" -name "*trade_review*" -o -path "*/db/migration/*" -name "*TradeReview*" | sort

echo
echo "[3] Read TradeReviewService createTradeReview method fully"
sed -n '28,90p' RomRom-Domain-Item/src/main/java/com/romrom/item/service/TradeReviewService.java

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 6439


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] Verify if reviewComment validation is in controller or interceptor"
find . -name "*TradeController.java" ! -path "*/Docs.java" -exec cat {} \;

Repository: TEAM-ROMROM/RomRom-BE

Length of output: 6363


DataIntegrityViolationException 전체를 중복후기로 매핑하면 오진됩니다.

Lines 80-84에서 모든 DataIntegrityViolationException을 TRADE_REVIEW_ALREADY_EXISTS로 변환하고 있습니다. 데이터베이스 마이그레이션(V1_4_58__add_trade_review_constraints.sql)에 따르면 trade_review 테이블에는 (trade_request_history_id, reviewer_member_id) 복합 유니크 제약 외에도 3개의 NOT NULL 제약이 있습니다. 또한 TradeRequest.reviewComment에는 @Size 검증이 없어서, 입력값 검증 우회 시 컬럼 길이 제약을 위반할 수 있습니다.

라인 59에서 existsByTradeRequestHistoryAndReviewerMember로 사전 검증하고 있으므로, catch 블록은 동시성 환경의 안전장치 역할입니다. 하지만 특정 유니크 제약 위반만 TRADE_REVIEW_ALREADY_EXISTS로 매핑하고, 나머지 무결성 제약 위반은 INVALID_REQUEST나 다른 적절한 에러코드로 처리해야 합니다.

수정 방향 예시
   try {
     tradeReviewRepository.save(tradeReview);
   } catch (DataIntegrityViolationException e) {
-    log.warn("중복 후기 저장 시도 (DB 유니크 제약 위반): tradeRequestHistoryId={}, memberId={}",
-        tradeRequestHistory.getTradeRequestHistoryId(), reviewerMember.getMemberId());
-    throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
+    String message = e.getMostSpecificCause() != null ? e.getMostSpecificCause().getMessage() : "";
+    if (message != null && message.contains("uk_trade_review_history_reviewer")) {
+      log.warn("중복 후기 저장 시도 (DB 유니크 제약 위반): tradeRequestHistoryId={}, memberId={}",
+          tradeRequestHistory.getTradeRequestHistoryId(), reviewerMember.getMemberId());
+      throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
+    }
+    throw new CustomException(ErrorCode.INVALID_REQUEST);
   }
📝 Committable suggestion

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

Suggested change
} catch (DataIntegrityViolationException e) {
log.warn("중복 후기 저장 시도 (DB 유니크 제약 위반): tradeRequestHistoryId={}, memberId={}",
tradeRequestHistory.getTradeRequestHistoryId(), reviewerMember.getMemberId());
throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
}
} catch (DataIntegrityViolationException e) {
String message = e.getMostSpecificCause() != null ? e.getMostSpecificCause().getMessage() : "";
if (message != null && message.contains("uk_trade_review_history_reviewer")) {
log.warn("중복 후기 저장 시도 (DB 유니크 제약 위반): tradeRequestHistoryId={}, memberId={}",
tradeRequestHistory.getTradeRequestHistoryId(), reviewerMember.getMemberId());
throw new CustomException(ErrorCode.TRADE_REVIEW_ALREADY_EXISTS);
}
throw new CustomException(ErrorCode.INVALID_REQUEST);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@RomRom-Domain-Item/src/main/java/com/romrom/item/service/TradeReviewService.java`
around lines 80 - 84, The catch block for DataIntegrityViolationException in
create/save flow is overly broad: change the handler in TradeReviewService so it
differentiates unique-constraint violations on (trade_request_history_id,
reviewer_member_id) from other integrity errors; inspect the exception (e.g.,
SQLState, error code, or constraint name available on the thrown
DataIntegrityViolationException / its root cause) and only map that specific
constraint to ErrorCode.TRADE_REVIEW_ALREADY_EXISTS (still include
tradeRequestHistory.getTradeRequestHistoryId() and reviewerMember.getMemberId()
in the log), while mapping other integrity violations (NOT NULL or
length/constraint breaches) to ErrorCode.INVALID_REQUEST or a more appropriate
code; keep the optimistic pre-check existsByTradeRequestHistoryAndReviewerMember
as-is and use this targeted constraint-name/code inspection inside the catch to
decide which CustomException to throw.

log.debug("후기 작성 완료: tradeReviewId={}", tradeReview.getTradeReviewId());
}

private TradeRequestHistory findTradeRequestHistoryById(UUID tradeRequestHistoryId) {
return tradeRequestHistoryRepository.findByTradeRequestHistoryIdWithItems(tradeRequestHistoryId)
.orElseThrow(() -> new CustomException(ErrorCode.TRADE_REQUEST_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.romrom.item.dto.TradeRequest;
import com.romrom.item.dto.TradeResponse;
import com.romrom.item.service.TradeRequestService;
import com.romrom.item.service.TradeReviewService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import me.suhsaechan.suhlogger.annotation.LogMonitor;
Expand All @@ -25,6 +26,7 @@
public class TradeController implements TradeControllerDocs {

private final TradeRequestService tradeRequestService;
private final TradeReviewService tradeReviewService;

@Override
@PostMapping(value = "/check", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Expand Down Expand Up @@ -139,4 +141,16 @@ public ResponseEntity<TradeResponse> getAiRecommendedItems(
request.setMember(customUserDetails.getMember());
return ResponseEntity.ok(tradeRequestService.getAiRecommendedItems(request));
}

@Override
@PostMapping(value = "/review/post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@LogMonitor
public ResponseEntity<Void> postTradeReview(
@AuthenticationPrincipal CustomUserDetails customUserDetails,
@ModelAttribute TradeRequest request) {
request.setMember(customUserDetails.getMember());
tradeReviewService.postTradeReview(request);
return ResponseEntity.ok().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1339,4 +1339,38 @@ ResponseEntity<TradeResponse> getSentTradeRequests(
"""
)
ResponseEntity<TradeResponse> getAiRecommendedItems(CustomUserDetails customUserDetails, TradeRequest tradeRequest);

@ApiChangeLogs({
@ApiChangeLog(date = "2026.04.06", author = Author.KIMNAYOUNG, issueNumber = 623, description = "거래 완료 후기 작성 " +
"API 구현"),
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
@Operation(
summary = "거래 완료 후기 작성",
description = """
## 인증(JWT): **필요**

## 요청 파라미터 (TradeRequest)
- **`tradeRequestHistoryId`**: 거래 요청 ID (UUID)
- **`tradeReviewRating`**: 종합 평가 (BAD/GOOD/GREAT)
- **`tradeReviewTags`**: 세부 항목 태그 목록 (복수 선택 가능)
- FAST_RESPONSE, GOOD_ITEM_CONDITION, MATCHES_PHOTO, PUNCTUAL, KIND
- **`reviewComment`**: 한마디

## 반환값
- 없음 (200 OK)

## 에러코드
- **`TRADE_REQUEST_NOT_FOUND`**: 거래 요청이 존재하지 않습니다.
- **`TRADE_NOT_COMPLETED`**: 거래가 완료되지 않아 후기를 작성할 수 없습니다.
- **`TRADE_REVIEW_ACCESS_FORBIDDEN`**: 해당 거래의 당사자만 후기를 작성할 수 있습니다.
- **`TRADE_REVIEW_ALREADY_EXISTS`**: 이미 해당 거래에 후기를 작성했습니다.

## 설명
- 거래 상태가 TRADED(거래 완료)인 경우에만 후기 작성 가능
- 거래 당사자(takeItem 소유자 또는 giveItem 소유자) 각자 1회씩 작성 가능
- 동일 거래에 대한 중복 후기 작성 방지
"""
)
ResponseEntity<Void> postTradeReview(CustomUserDetails customUserDetails, TradeRequest tradeRequest);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
-- trade_review 테이블 무결성 제약 추가
-- tradeRequestHistory → trade_request_history_trade_request_history_id
-- reviewerMember → reviewer_member_member_id
-- reviewedMember → reviewed_member_member_id
-- 1. FK 컬럼 NOT NULL 제약
-- 2. (trade_request_history_trade_request_history_id, reviewer_member_member_id) 복합 유니크 제약 → 동시성 환경에서 중복 후기 방지
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'trade_review'
) THEN

-- trade_request_history_trade_request_history_id NOT NULL
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'trade_review'
AND column_name = 'trade_request_history_trade_request_history_id'
AND is_nullable = 'YES'
) THEN
ALTER TABLE public.trade_review
ALTER COLUMN trade_request_history_trade_request_history_id SET NOT NULL;
RAISE NOTICE 'trade_request_history_trade_request_history_id NOT NULL 제약을 추가했습니다';
END IF;

-- reviewer_member_member_id NOT NULL
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'trade_review'
AND column_name = 'reviewer_member_member_id'
AND is_nullable = 'YES'
) THEN
ALTER TABLE public.trade_review
ALTER COLUMN reviewer_member_member_id SET NOT NULL;
RAISE NOTICE 'reviewer_member_member_id NOT NULL 제약을 추가했습니다';
END IF;

-- reviewed_member_member_id NOT NULL
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'trade_review'
AND column_name = 'reviewed_member_member_id'
AND is_nullable = 'YES'
) THEN
ALTER TABLE public.trade_review
ALTER COLUMN reviewed_member_member_id SET NOT NULL;
RAISE NOTICE 'reviewed_member_member_id NOT NULL 제약을 추가했습니다';
END IF;

-- (trade_request_history_trade_request_history_id, reviewer_member_member_id) 복합 유니크 제약
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND constraint_name = 'uk_trade_review_history_reviewer'
AND table_name = 'trade_review'
) THEN
ALTER TABLE public.trade_review
ADD CONSTRAINT uk_trade_review_history_reviewer
UNIQUE (trade_request_history_trade_request_history_id, reviewer_member_member_id);
RAISE NOTICE 'uk_trade_review_history_reviewer 유니크 제약을 추가했습니다';
ELSE
RAISE NOTICE 'uk_trade_review_history_reviewer 이미 존재합니다. 스킵합니다';
END IF;

ELSE
RAISE NOTICE 'trade_review 테이블이 존재하지 않습니다. JPA가 생성할 예정입니다';
END IF;

EXCEPTION
WHEN OTHERS THEN
RAISE WARNING '마이그레이션 중 오류 발생: %', SQLERRM;
END $$;