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 @@ -39,7 +39,7 @@ public class ChatController {
public BaseResponse<ChatRoomResponse> findOrCreateChatRoom(@RequestBody @Valid ChatRoomRequest chatRoomRequest) {

ChatRoomResponse chatRoomResponse = chatRoomService
.findOrCreateChatRoom(currentUserProvider.getCurrentUserId(), chatRoomRequest.getItemId());
.findOrCreateChatRoom(currentUserProvider.getCurrentUserId(), chatRoomRequest.getItemId(), chatRoomRequest.getPurchaseOfferId());

return BaseResponse.success(chatRoomResponse);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ public class ChatRoomRequest {

@NotNull(message = "아이템 id는 필수입니다.")
private Long itemId;

// 판매자가 채팅방을 생성할 때 사용. 구매자 입장에서는 null.
private Long purchaseOfferId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import com.tenure.domain.item.entity.Item;
import com.tenure.domain.product.entity.Product;
import com.tenure.domain.product.enums.ProductStatus;

import static com.tenure.domain.product.enums.ProductStatus.ON_SALE;
import static com.tenure.domain.product.enums.ProductStatus.TRADING;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -39,7 +42,8 @@ public class ChatRoomResponse {
public static ChatRoomResponse from(
ChatRoom chatRoom, Item item, Product product,
Long currentUserId, Long tradeId, Long purchaseIntentId,
Long purchaseOfferId, boolean isBlocked, boolean isOpponentExited)
Long purchaseOfferId, boolean isBlocked, boolean isOpponentExited,
Integer offerPrice)
{
boolean isBuyer = currentUserId.equals(chatRoom.getBuyer().getId());

Expand All @@ -56,7 +60,12 @@ public static ChatRoomResponse from(
: chatRoom.getBuyer().getProfileImageUrl();

ProductStatus productStatus = product != null ? product.getProductStatus() : null;
Integer price = product != null ? product.getPrice() : null;


// 판매중/거래중이면 상품 가격, 그 외(미판매 + product없음)는 제안 금액(없으면 null)
Integer price = (product != null && (product.getProductStatus() == ON_SALE || product.getProductStatus() == TRADING))
? product.getPrice()
: offerPrice;
Long productId = product != null ? product.getId() : null;

return new ChatRoomResponse(chatRoom.getId(), opponentUserId, opponentUsername, opponentProfileImage, item.getId(),
Expand Down
64 changes: 50 additions & 14 deletions src/main/java/com/tenure/domain/chat/service/ChatRoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import com.tenure.domain.product.entity.Product;
import com.tenure.domain.product.exception.ProductErrorCode;
import com.tenure.domain.product.repository.ProductRepository;
import com.tenure.domain.purchase.entity.PurchaseOffer;
import com.tenure.domain.purchase.enums.PurchaseIntentStatus;
import com.tenure.domain.purchase.enums.PurchaseOfferStatus;
import com.tenure.domain.purchase.exception.PurchaseOfferErrorCode;
import com.tenure.domain.purchase.repository.PurchaseIntentRepository;
import com.tenure.domain.purchase.repository.PurchaseOfferRepository;
import com.tenure.domain.trade.entity.Trade;
Expand Down Expand Up @@ -80,9 +82,9 @@ public class ChatRoomService {

// 채팅방 조회 / 생성
@Transactional
public ChatRoomResponse findOrCreateChatRoom(Long buyerId, Long itemId) {
public ChatRoomResponse findOrCreateChatRoom(Long currentUserId, Long itemId, Long purchaseOfferId) {

log.info("[채팅방 생성/조회] buyerId = {}, itemId = {}", buyerId, itemId);
log.info("[채팅방 생성/조회] currentUserId = {}, itemId = {}", currentUserId, itemId);

//아이템 + 주인 한 번에 조회
Item item = itemRepository.findByIdWithOwner(itemId)
Expand All @@ -93,16 +95,42 @@ public ChatRoomResponse findOrCreateChatRoom(Long buyerId, Long itemId) {

User owner = item.getOwner();

//구매자(사용자) 조회
User buyer = userRepository.findById(buyerId)
.orElseThrow(() -> {
log.warn("[채팅방 생성/조회] 해당 구매자는 존재하지 않습니다. buyerId = {}", buyerId);
return new CustomException(UserErrorCode.USER_NOT_FOUND);
});
Long buyerId;
User buyer;

// 판매자라면
if (currentUserId.equals(owner.getId())) {
// 판매자가 채팅 시작 — purchaseOfferId로 구매자 특정
if (purchaseOfferId == null) {
log.warn("[채팅방 생성/조회] 판매자가 채팅방 생성 시 purchaseOfferId 필요. currentUserId = {}", currentUserId);
throw new CustomException(ChatErrorCode.CHAT_CREATION_NOT_ALLOWED);
}
PurchaseOffer offer = purchaseOfferRepository.findByIdWithUsers(purchaseOfferId)
.orElseThrow(() -> {
log.warn("[채팅방 생성/조회] 구매 제안을 찾을 수 없습니다. purchaseOfferId = {}", purchaseOfferId);
return new CustomException(PurchaseOfferErrorCode.PURCHASE_OFFER_NOT_FOUND);
});

// 검증
boolean valid = offer.getItem().getId().equals(itemId)
&& offer.getOwner().getId().equals(currentUserId)
&& (offer.getStatus() == SENT || offer.getStatus() == ACCEPTED);

if (!valid) {
log.warn("[채팅방 생성/조회] 유효하지 않은 구매 제안. purchaseOfferId = {}", purchaseOfferId);
throw new CustomException(ChatErrorCode.CHAT_CREATION_NOT_ALLOWED);
}

if(buyerId.equals(owner.getId())) {
log.warn("[채팅방 생성 / 조회] 본인의 아이템에는 채팅방을 생성 할 수없습니다. buyerId = {}, ownerId = {}", buyerId, owner.getId());
throw new CustomException(ChatErrorCode.CHAT_CREATION_NOT_ALLOWED);
buyer = offer.getProposer();
buyerId = buyer.getId();
} else {
// 구매자가 채팅 시작
buyerId = currentUserId;
buyer = userRepository.findById(buyerId)
.orElseThrow(() -> {
log.warn("[채팅방 생성/조회] 해당 구매자는 존재하지 않습니다. buyerId = {}", buyerId);
return new CustomException(UserErrorCode.USER_NOT_FOUND);
});
}

// 양방향 차단 한 번에 조회
Expand Down Expand Up @@ -172,13 +200,17 @@ public ChatRoomResponse findOrCreateChatRoom(Long buyerId, Long itemId) {
.findIdByBuyerIdAndSellerIdAndProductIdAndStatus(buyerId, owner.getId(), product.getId(), PurchaseIntentStatus.SENT)
.orElse(null);

Long purchaseOfferId = purchaseOfferRepository
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);

// 아이템 상세에서 바로 들어온 경우 처음엔 isOpponentExited false 고정
return ChatRoomResponse
.from(chatRoom, item, product, buyerId, tradeId, purchaseIntentId, purchaseOfferId, ownerBlockedBuyer, false);
.from(chatRoom, item, product, currentUserId, tradeId, purchaseIntentId, sentPurchaseOfferId, ownerBlockedBuyer, false, offerPrice);
}

// 채팅방 목록 조회
Expand Down Expand Up @@ -254,6 +286,10 @@ public ChatRoomResponse enterChatroom(Long currentUserId, Long chatRoomId) {
.findIdByProposerIdAndOwnerIdAndItemIdAndStatus(buyerId, sellerId, item.getId(), SENT)
.orElse(null);

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

// 상대방이 나를 차단했는지 여부
boolean isBlocked = userBlockRepository.isBlocked(opponentId, currentUserId);

Expand All @@ -267,7 +303,7 @@ public ChatRoomResponse enterChatroom(Long currentUserId, Long chatRoomId) {
.ifPresent(Notification::markRead);

return ChatRoomResponse
.from(chatRoom, item, product, currentUserId, tradeId, purchaseIntentId, purchaseOfferId, isBlocked, isOpponentExited);
.from(chatRoom, item, product, currentUserId, tradeId, purchaseIntentId, purchaseOfferId, isBlocked, isOpponentExited, offerPrice);
}

//채팅방 접속 시 unreadCount 업데이트
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ List<ExpiredPurchaseOfferTarget> findExpiredSentTargets(
@Query("select offer from PurchaseOffer offer where offer.id = :offerId")
Optional<PurchaseOffer> findByIdForUpdate(@Param("offerId") Long offerId);

@Query("select offer from PurchaseOffer offer join fetch offer.item join fetch offer.owner join fetch offer.proposer where offer.id = :offerId")
Optional<PurchaseOffer> findByIdWithUsers(@Param("offerId") Long offerId);

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("""
select offer
Expand Down Expand Up @@ -242,6 +245,13 @@ Optional<Long> findIdByProposerIdAndOwnerIdAndItemIdAndStatus(
@Param("itemId") Long itemId,
@Param("status") PurchaseOfferStatus status);

@Query("select po.offerPrice from PurchaseOffer po where po.proposer.id = :proposerId and po.owner.id = :ownerId and po.item.id = :itemId and po.status in :statuses")
Optional<Integer> findOfferPriceByProposerIdAndOwnerIdAndItemIdAndStatusIn(
@Param("proposerId") Long proposerId,
@Param("ownerId") Long ownerId,
@Param("itemId") Long itemId,
@Param("statuses") List<PurchaseOfferStatus> statuses);

@Query("select count(po) > 0 from PurchaseOffer po where po.proposer.id = :proposerId and po.owner.id = :ownerId and po.item.id = :itemId and po.status in :statuses")
boolean existsByProposerIdAndOwnerIdAndItemIdAndStatusIn(
@Param("proposerId") Long proposerId,
Expand Down
Loading