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
Expand Up @@ -76,6 +76,17 @@ public BaseResponse<ChatRoomResponse> getChatRoom(@PathVariable Long chatRoomId)
return BaseResponse.success(chatRoomResponse);
}

@Operation(
summary = "거래 기반 채팅방 생성 또는 조회",
description = "거래 상세 화면에서 채팅하기 버튼 클릭 시 호출합니다. 구매자/판매자 모두 사용 가능합니다."
)
@PostMapping("/trades/{tradeId}")
public BaseResponse<ChatRoomResponse> findOrCreateChatRoomByTrade(@PathVariable Long tradeId) {
ChatRoomResponse chatRoomResponse = chatRoomService
.findOrCreateChatRoomByTrade(currentUserProvider.getCurrentUserId(), tradeId);
return BaseResponse.success(chatRoomResponse);
}

@Operation(
summary = "채팅방 읽음 처리",
description = "채팅방 접속 시 호출합니다. 읽지 않은 메시지 수를 0으로 초기화합니다."
Expand Down
97 changes: 82 additions & 15 deletions src/main/java/com/tenure/domain/chat/service/ChatRoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.tenure.domain.purchase.repository.PurchaseIntentRepository;
import com.tenure.domain.purchase.repository.PurchaseOfferRepository;
import com.tenure.domain.trade.entity.Trade;
import com.tenure.domain.trade.exception.TradeErrorCode;
import com.tenure.domain.trade.repository.TradeRepository;
import com.tenure.domain.user.entity.User;
import com.tenure.domain.user.entity.UserBlock;
Expand All @@ -38,6 +39,7 @@
import com.tenure.global.storage.ImageStorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jspecify.annotations.NonNull;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
Expand Down Expand Up @@ -176,21 +178,7 @@ public ChatRoomResponse findOrCreateChatRoom(Long currentUserId, Long itemId, Lo
return offerSentProduct;
});

ChatRoom chatRoom;

// 사용자가 동시에 채팅방 생성을 할 경우 방지
try {
//체팅방을 조회(닫히지 않은 채팅방) / 없으면 새로 만든 후 저장
chatRoom = chatRoomRepository.findByItemIdAndSellerIdAndBuyerIdAndIsClosedFalse(itemId, owner.getId(), buyerId)
.orElseGet(() -> createChatRoom(item, buyer, owner));
} catch (DataIntegrityViolationException e) {
chatRoom = chatRoomRepository.findByItemIdAndSellerIdAndBuyerIdAndIsClosedFalse(itemId, owner.getId(), buyerId)
.orElseThrow(() -> {
log.warn("[채팅방 생성/조회] 채팅방을 찾을 수 없습니다. itemId = {}, ownerId = {}, buyerId = {}", itemId, owner.getId(), buyerId );
return new CustomException(ChatErrorCode.CHAT_ROOM_NOT_FOUND);
});
}

ChatRoom chatRoom = findOrCreateChatRoom(itemId, owner, buyerId, item, buyer);


Long tradeId = tradeRepository.findByItemId(itemId).map(Trade::getId).orElse(null);
Expand All @@ -213,6 +201,85 @@ public ChatRoomResponse findOrCreateChatRoom(Long currentUserId, Long itemId, Lo
.from(chatRoom, item, product, currentUserId, tradeId, purchaseIntentId, sentPurchaseOfferId, ownerBlockedBuyer, false, offerPrice);
}

// 거래 기반 채팅방 조회 / 생성 (TradeDetailPage)
@Transactional
public ChatRoomResponse findOrCreateChatRoomByTrade(Long currentUserId, Long tradeId) {

log.info("[거래 기반 채팅방 생성/조회] currentUserId = {}, tradeId = {}", currentUserId, tradeId);

Trade trade = tradeRepository.findByIdWithParticipants(tradeId)
.orElseThrow(() -> {
log.warn("[거래 기반 채팅방 생성/조회] 거래를 찾을 수 없습니다. tradeId = {}", tradeId);
return new CustomException(TradeErrorCode.TRADE_NOT_FOUND);
});

// 구매자, 판매자, 아이템 조회
User buyer = trade.getBuyer();
User owner = trade.getSeller();
Long buyerId = buyer.getId();
Item item = trade.getItem();
Long itemId = item.getId();

// 거래 참여자인지 검증
boolean isBuyer = currentUserId.equals(buyerId);
boolean isSeller = currentUserId.equals(owner.getId());
if (!isBuyer && !isSeller) {
log.warn("[거래 기반 채팅방 생성/조회] 거래 참여자가 아닙니다. currentUserId = {}", currentUserId);
throw new CustomException(ChatErrorCode.CHAT_CREATION_NOT_ALLOWED);
}

// 양방향 차단 조회
List<UserBlock> blocks = userBlockRepository.findBlocksBetween(buyerId, owner.getId());

boolean buyerBlockedOwner = blocks.stream()
.anyMatch(b -> b.getBlocker().getId().equals(buyerId));

boolean ownerBlockedBuyer = blocks.stream()
.anyMatch(b -> b.getBlocker().getId().equals(owner.getId()));

if (buyerBlockedOwner) {
log.warn("[거래 기반 채팅방 생성/조회] 차단된 사용자와는 채팅 할 수 없습니다.");
throw new CustomException(ChatErrorCode.CHAT_BLOCKED);
}

// 채팅방 find or create
ChatRoom chatRoom = findOrCreateChatRoom(itemId, owner, buyerId, item, buyer);

// product 조회 (상태 무관 — 거래 성사 후이므로 SOLD 포함 허용)
Product product = productRepository.findByItemId(itemId).orElse(null);

Long purchaseIntentId = (product == null) ? null : purchaseIntentRepository
.findIdByBuyerIdAndSellerIdAndProductIdAndStatus(buyerId, owner.getId(), product.getId(), PurchaseIntentStatus.SENT)
.orElse(null);

Long sentPurchaseOfferId = purchaseOfferRepository
.findIdByProposerIdAndOwnerIdAndItemIdAndStatus(buyerId, owner.getId(), itemId, SENT)
.orElse(null);

Integer offerPrice = purchaseOfferRepository
.findOfferPriceByProposerIdAndOwnerIdAndItemIdAndStatusIn(buyerId, owner.getId(), itemId, List.of(SENT, ACCEPTED))
.orElse(null);

return ChatRoomResponse
.from(chatRoom, item, product, currentUserId, tradeId, purchaseIntentId, sentPurchaseOfferId, ownerBlockedBuyer, false, offerPrice);
}

// 기존 채팅방이 있으면 반환, 없으면 생성
private @NonNull ChatRoom findOrCreateChatRoom(Long itemId, User owner, Long buyerId, Item item, User buyer) {
ChatRoom chatRoom;
try {
chatRoom = chatRoomRepository.findByItemIdAndSellerIdAndBuyerIdAndIsClosedFalse(itemId, owner.getId(), buyerId)
.orElseGet(() -> createChatRoom(item, buyer, owner));
} catch (DataIntegrityViolationException e) {
chatRoom = chatRoomRepository.findByItemIdAndSellerIdAndBuyerIdAndIsClosedFalse(itemId, owner.getId(), buyerId)
.orElseThrow(() -> {
log.warn("[거래 기반 채팅방 생성/조회] 채팅방을 찾을 수 없습니다. itemId = {}, ownerId = {}, buyerId = {}", itemId, owner.getId(), buyerId);
return new CustomException(ChatErrorCode.CHAT_ROOM_NOT_FOUND);
});
}
return chatRoom;
}

// 채팅방 목록 조회
public ChatRoomListCursorResponse chatRoomList(Long currentUserId, ChatRoomFilterType type,
LocalDateTime cursor, LocalDateTime createdAtCursor, Long cursorId, int size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,14 @@ List<Trade> findAllBySourceTypeAndSourceIdIn(
boolean existsByItemIdAndStatusNotIn(Long itemId, Collection<TradeStatus> statuses);

Optional<Trade> findByItemId(Long itemId);

@Query("""
select trade
from Trade trade
join fetch trade.buyer
join fetch trade.seller
join fetch trade.item
where trade.id = :tradeId
""")
Optional<Trade> findByIdWithParticipants(@Param("tradeId") Long tradeId);
}
Loading