Skip to content

Commit

Permalink
MATE-94 : [REFACTOR] 굿즈거래 채팅방 입장 기능 리팩토링 (#86)
Browse files Browse the repository at this point in the history
* MATE-94: [RENAME] 굿즈거래 채팅내역 조회 메서드 네이밍 수정

* MATE-94 : [FEAT] 굿즈거래 채팅 전송 검증 기능 추가

* MATE-94 : [REFACTOR] 굿즈거래 채팅방 입장 기능 리팩토링

- Response DTO 최신 채팅내역 20개 추가

* MATE-94 : [REFACTOR] 굿즈거래 채팅 관련 api path 변경

* MATE-94 : [CHORE] 불필요한 주석 제거
  • Loading branch information
hongjeZZ authored Dec 5, 2024
1 parent 0972ef5 commit b179c34
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;

@Controller
@RequiredArgsConstructor
Expand All @@ -14,7 +15,7 @@ public class GoodsChatMessageController {
private final GoodsChatMessageService goodsChatMessageService;

@MessageMapping("/chat/goods/message")
public void handleMessage(@Payload GoodsChatMessageRequest message) {
public void handleMessage(@Validated @Payload GoodsChatMessageRequest message) {
goodsChatMessageService.sendMessage(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.mate.common.response.ApiResponse;
import com.example.mate.common.response.PageResponse;
import com.example.mate.common.security.auth.AuthMember;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomResponse;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomSummaryResponse;
Expand All @@ -12,6 +13,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,67 +29,48 @@ public class GoodsChatRoomController {

private final GoodsChatService goodsChatService;

/*
굿즈거래 상세 페이지 - 채팅방 입장
TODO: @RequestParam Long memberId -> @AuthenticationPrincipal 로 변경
"/api/goods/chat" 로 변경 예정
*/
@PostMapping
public ResponseEntity<ApiResponse<GoodsChatRoomResponse>> createGoodsChatRoom(@RequestParam Long buyerId,
public ResponseEntity<ApiResponse<GoodsChatRoomResponse>> createGoodsChatRoom(@AuthenticationPrincipal AuthMember member,
@RequestParam Long goodsPostId) {
GoodsChatRoomResponse response = goodsChatService.getOrCreateGoodsChatRoom(buyerId, goodsPostId);
GoodsChatRoomResponse response = goodsChatService.getOrCreateGoodsChatRoom(member.getMemberId(), goodsPostId);
return ResponseEntity.ok(ApiResponse.success(response));
}

/*
굿즈거래 채팅방 페이지 - 채팅 내역 조회
TODO: @RequestParam Long memberId -> @AuthenticationPrincipal 로 변경
*/
@GetMapping("/{chatRoomId}/message")
public ResponseEntity<ApiResponse<PageResponse<GoodsChatMessageResponse>>> getGoodsChatRoomMessages(
@AuthenticationPrincipal AuthMember member,
@PathVariable Long chatRoomId,
@RequestParam Long memberId,
@PageableDefault Pageable pageable
@PageableDefault(page = 1, size = 20) Pageable pageable
) {
PageResponse<GoodsChatMessageResponse> response = goodsChatService.getMessagesForChatRoom(chatRoomId, memberId, pageable);
PageResponse<GoodsChatMessageResponse> response = goodsChatService.getMessagesForChatRoom(chatRoomId, member.getMemberId(), pageable);
return ResponseEntity.ok(ApiResponse.success(response));
}

/*
굿즈거래 채팅방 리스트 페이지 - 내가 참여한 채팅방 리스트 조회
TODO: @RequestParam Long memberId -> @AuthenticationPrincipal 로 변경
*/
@GetMapping
public ResponseEntity<ApiResponse<PageResponse<GoodsChatRoomSummaryResponse>>> getGoodsChatRooms(@RequestParam Long memberId,
public ResponseEntity<ApiResponse<PageResponse<GoodsChatRoomSummaryResponse>>> getGoodsChatRooms(@AuthenticationPrincipal AuthMember member,
@PageableDefault Pageable pageable) {
PageResponse<GoodsChatRoomSummaryResponse> response = goodsChatService.getGoodsChatRooms(memberId, pageable);
PageResponse<GoodsChatRoomSummaryResponse> response = goodsChatService.getGoodsChatRooms(member.getMemberId(), pageable);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 채팅방 나가기
@DeleteMapping("/{chatRoomId}")
public ResponseEntity<Void> leaveGoodsChatRoom(@RequestParam Long memberId, @PathVariable Long chatRoomId) {
goodsChatService.deactivateGoodsChatPart(memberId, chatRoomId);
public ResponseEntity<Void> leaveGoodsChatRoom(@AuthenticationPrincipal AuthMember member, @PathVariable Long chatRoomId) {
goodsChatService.deactivateGoodsChatPart(member.getMemberId(), chatRoomId);

return ResponseEntity.noContent().build();
}

/*
굿즈거래 채팅방 리스트 페이지 - 채팅방 단건 조회
TODO: @RequestParam Long memberId -> @AuthenticationPrincipal 로 변경
*/
@GetMapping("/{chatRoomId}")
public ResponseEntity<ApiResponse<GoodsChatRoomResponse>> getGoodsChatRoomInfo(@RequestParam Long memberId,
public ResponseEntity<ApiResponse<GoodsChatRoomResponse>> getGoodsChatRoomInfo(@AuthenticationPrincipal AuthMember member,
@PathVariable Long chatRoomId) {
GoodsChatRoomResponse response = goodsChatService.getGoodsChatRoomInfo(memberId, chatRoomId);
GoodsChatRoomResponse response = goodsChatService.getGoodsChatRoomInfo(member.getMemberId(), chatRoomId);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 채팅방 하단 토글 - 현재 채팅에 참여한 사용자 프로필 조회
@GetMapping("/{chatRoomId}/members")
public ResponseEntity<ApiResponse<List<MemberSummaryResponse>>> getGoodsChatRoomMembers(@RequestParam Long memberId,
public ResponseEntity<ApiResponse<List<MemberSummaryResponse>>> getGoodsChatRoomMembers(@AuthenticationPrincipal AuthMember member,
@PathVariable Long chatRoomId) {
List<MemberSummaryResponse> responses = goodsChatService.getChatRoomMembers(memberId, chatRoomId);
List<MemberSummaryResponse> responses = goodsChatService.getChatRoomMembers(member.getMemberId(), chatRoomId);
return ResponseEntity.ok(ApiResponse.success(responses));
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package com.example.mate.domain.goodsChat.dto.request;

import com.example.mate.domain.constant.MessageType;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;

@Getter
public class GoodsChatMessageRequest {

@NotNull(message = "채팅방 ID는 필수 입력 값입니다.")
private Long roomId;

@NotNull(message = "회원 ID는 필수 입력 값입니다.")
private Long senderId;

@NotBlank(message = "메시지는 비어있을 수 없습니다.")
private String message;

@NotNull(message = "채팅 타입은 필수 입력 값입니다.")
private MessageType type;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.mate.domain.goodsChat.dto.response;

import com.example.mate.common.response.PageResponse;
import com.example.mate.domain.constant.TeamInfo;
import com.example.mate.domain.goods.entity.GoodsPost;
import com.example.mate.domain.goodsChat.entity.GoodsChatRoom;
Expand All @@ -22,7 +23,9 @@ public class GoodsChatRoomResponse {
private final String chatRoomStatus;
private final String imageUrl;

public static GoodsChatRoomResponse of(GoodsChatRoom chatRoom) {
private final PageResponse<GoodsChatMessageResponse> initialMessages;

public static GoodsChatRoomResponse of(GoodsChatRoom chatRoom, PageResponse<GoodsChatMessageResponse> initialMessages) {
GoodsPost goodsPost = chatRoom.getGoodsPost();
String mainImageUrl = goodsPost.getMainImageUrl();
String teamName = getTeamName(goodsPost);
Expand All @@ -37,6 +40,7 @@ public static GoodsChatRoomResponse of(GoodsChatRoom chatRoom) {
.imageUrl(mainImageUrl)
.postStatus(goodsPost.getStatus().getValue())
.chatRoomStatus(chatRoom.getIsActive().toString())
.initialMessages(initialMessages)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
public interface GoodsChatMessageRepository extends JpaRepository<GoodsChatMessage, Long> {

@Query("""
SELECT cm
FROM GoodsChatMessage cm
WHERE cm.goodsChatPart.goodsChatRoom.id = :chatRoomId
ORDER BY cm.sentAt DESC
""")
Page<GoodsChatMessage> findByChatRoomId(@Param("chatRoomId") Long chatRoomId, Pageable pageable);
SELECT cm
FROM GoodsChatMessage cm
WHERE cm.goodsChatPart.goodsChatRoom.id = :chatRoomId
ORDER BY cm.sentAt DESC
""")
Page<GoodsChatMessage> getChatMessages(@Param("chatRoomId") Long chatRoomId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ public class GoodsChatMessageService {
private final GoodsChatMessageRepository messageRepository;
private final SimpMessagingTemplate messagingTemplate;

private static final String MEMBER_ENTER_MESSAGE = "님이 채팅방에 입장하셨습니다.";
private static final String MEMBER_LEAVE_MESSAGE = "님이 채팅방을 떠나셨습니다.";

private static final String MEMBER_ENTER_MESSAGE = "님이 대화를 시작했습니다.";
private static final String MEMBER_LEAVE_MESSAGE = "님이 대화를 떠났습니다.";

public void sendMessage(GoodsChatMessageRequest message) {
Member sender = findMemberById(message.getSenderId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
import com.example.mate.domain.goods.entity.Role;
import com.example.mate.domain.goods.entity.Status;
import com.example.mate.domain.goods.repository.GoodsPostRepository;
import com.example.mate.domain.goodsChat.event.GoodsChatEvent;
import com.example.mate.domain.goodsChat.event.GoodsChatEventPublisher;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomResponse;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomSummaryResponse;
import com.example.mate.domain.goodsChat.entity.GoodsChatMessage;
import com.example.mate.domain.goodsChat.entity.GoodsChatPart;
import com.example.mate.domain.goodsChat.entity.GoodsChatPartId;
import com.example.mate.domain.goodsChat.entity.GoodsChatRoom;
import com.example.mate.domain.goodsChat.event.GoodsChatEvent;
import com.example.mate.domain.goodsChat.event.GoodsChatEventPublisher;
import com.example.mate.domain.goodsChat.repository.GoodsChatMessageRepository;
import com.example.mate.domain.goodsChat.repository.GoodsChatPartRepository;
import com.example.mate.domain.goodsChat.repository.GoodsChatRoomRepository;
Expand All @@ -27,6 +27,7 @@
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -51,56 +52,52 @@ public GoodsChatRoomResponse getOrCreateGoodsChatRoom(Long buyerId, Long goodsPo
validateCreateChatRoom(goodsPost, buyer, seller);

// 구매자가 채팅방이 존재하면 기존 채팅방을 반환하고, 없다면 새로 생성하여 반환
GoodsChatRoom goodsChatRoom = chatRoomRepository.findExistingChatRoom(goodsPostId, buyerId, Role.BUYER)
return chatRoomRepository.findExistingChatRoom(goodsPostId, buyerId, Role.BUYER)
.map(this::buildChatRoomResponse)
.orElseGet(() -> createChatRoom(goodsPost, buyer, seller));

return GoodsChatRoomResponse.of(goodsChatRoom);
}

private void validateCreateChatRoom(GoodsPost goodsPost, Member seller, Member buyer) {
if (goodsPost.getStatus() == Status.CLOSED) {
throw new CustomException(ErrorCode.GOODS_CHAT_CLOSED_POST);
}
if (seller == buyer) {
throw new CustomException(ErrorCode.GOODS_CHAT_SELLER_CANNOT_START);
}
// 기존 채팅방 & 채팅 내역 반환 (최신 20개)
private GoodsChatRoomResponse buildChatRoomResponse(GoodsChatRoom chatRoom) {
Page<GoodsChatMessage> messages = messageRepository.getChatMessages(chatRoom.getId(), PageRequest.of(0, 20));
List<GoodsChatMessageResponse> content = messages.getContent().stream()
.map(GoodsChatMessageResponse::of)
.toList();
return GoodsChatRoomResponse.of(chatRoom, PageResponse.from(messages, content));
}

private GoodsChatRoom createChatRoom(GoodsPost goodsPost, Member buyer, Member seller) {
// 새로운 채팅방 반환
private GoodsChatRoomResponse createChatRoom(GoodsPost goodsPost, Member buyer, Member seller) {
GoodsChatRoom goodsChatRoom = GoodsChatRoom.builder()
.goodsPost(goodsPost)
.build();

GoodsChatRoom savedChatRoom = chatRoomRepository.save(goodsChatRoom);
savedChatRoom.addChatParticipant(buyer, Role.BUYER);
savedChatRoom.addChatParticipant(seller, Role.SELLER);

// 새로운 채팅방 생성 - 입장 메시지 전송
eventPublisher.publish(GoodsChatEvent.from(goodsChatRoom.getId(), buyer, MessageType.ENTER));

return savedChatRoom;
}

private Member findMemberById(Long memberId) {
return memberRepository.findById(memberId).orElseThrow(()
-> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_ID));
return GoodsChatRoomResponse.of(savedChatRoom, null);
}

private GoodsPost findGoodsPostById(Long goodsPostId) {
return goodsPostRepository.findById(goodsPostId).orElseThrow(() ->
new CustomException(ErrorCode.GOODS_NOT_FOUND_BY_ID));
private void validateCreateChatRoom(GoodsPost goodsPost, Member seller, Member buyer) {
if (goodsPost.getStatus() == Status.CLOSED) {
throw new CustomException(ErrorCode.GOODS_CHAT_CLOSED_POST);
}
if (seller == buyer) {
throw new CustomException(ErrorCode.GOODS_CHAT_SELLER_CANNOT_START);
}
}

@Transactional(readOnly = true)
public PageResponse<GoodsChatMessageResponse> getMessagesForChatRoom(Long chatRoomId, Long memberId, Pageable pageable) {
validateMemberParticipation(memberId, chatRoomId);
Pageable validatePageable = PageResponse.validatePageable(pageable);

Page<GoodsChatMessage> chatMessagePage = messageRepository.findByChatRoomId(chatRoomId, validatePageable);
Page<GoodsChatMessage> chatMessagePage = messageRepository.getChatMessages(chatRoomId, pageable);
List<GoodsChatMessageResponse> content = chatMessagePage.getContent().stream()
.map(GoodsChatMessageResponse::of)
.toList();

return PageResponse.from(chatMessagePage, content);
}

Expand All @@ -113,10 +110,7 @@ private void validateMemberParticipation(Long memberId, Long chatRoomId) {
@Transactional(readOnly = true)
public PageResponse<GoodsChatRoomSummaryResponse> getGoodsChatRooms(Long memberId, Pageable pageable) {
Member member = findMemberById(memberId);
Pageable validatePageable = PageResponse.validatePageable(pageable);

Page<GoodsChatRoom> chatRoomPage = chatRoomRepository.findChatRoomPageByMemberId(memberId, validatePageable);

Page<GoodsChatRoom> chatRoomPage = chatRoomRepository.findChatRoomPageByMemberId(memberId, pageable);
List<GoodsChatRoomSummaryResponse> content = chatRoomPage.getContent().stream()
.map(chatRoom -> GoodsChatRoomSummaryResponse.of(chatRoom, getOpponentMember(chatRoom, member)))
.toList();
Expand All @@ -136,10 +130,14 @@ private Member getOpponentMember(GoodsChatRoom chatRoom, Member member) {
@Transactional(readOnly = true)
public GoodsChatRoomResponse getGoodsChatRoomInfo(Long memberId, Long chatRoomId) {
validateMemberParticipation(memberId, chatRoomId);
GoodsChatRoom chatRoom = findChatRoomById(chatRoomId);

GoodsChatRoom goodsChatRoom = chatRoomRepository.findByChatRoomId(chatRoomId)
.orElseThrow(() -> new CustomException(ErrorCode.GOODS_CHAT_ROOM_NOT_FOUND));
return GoodsChatRoomResponse.of(goodsChatRoom);
Page<GoodsChatMessage> messages = messageRepository.getChatMessages(chatRoom.getId(), PageRequest.of(0, 20));
List<GoodsChatMessageResponse> content = messages.getContent().stream()
.map(GoodsChatMessageResponse::of)
.toList();

return GoodsChatRoomResponse.of(chatRoom, PageResponse.from(messages, content));
}

@Transactional(readOnly = true)
Expand All @@ -165,4 +163,19 @@ public void deactivateGoodsChatPart(Long memberId, Long chatRoomId) {
chatRoomRepository.deleteById(chatRoomId);
}
}

private GoodsChatRoom findChatRoomById(Long chatRoomId) {
return chatRoomRepository.findByChatRoomId(chatRoomId)
.orElseThrow(() -> new CustomException(ErrorCode.GOODS_CHAT_ROOM_NOT_FOUND));
}

private Member findMemberById(Long memberId) {
return memberRepository.findById(memberId).orElseThrow(()
-> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_ID));
}

private GoodsPost findGoodsPostById(Long goodsPostId) {
return goodsPostRepository.findById(goodsPostId).orElseThrow(() ->
new CustomException(ErrorCode.GOODS_NOT_FOUND_BY_ID));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.example.mate.common.response.PageResponse;
import com.example.mate.config.WithAuthMember;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse;
import com.example.mate.common.security.util.JwtUtil;
import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomResponse;
Expand Down Expand Up @@ -41,6 +42,7 @@ class GoodsChatRoomControllerTest {

@Test
@DisplayName("굿즈거래 채팅방 생성 성공 - 기존 채팅방이 있을 경우 해당 채팅방을 반환한다.")
@WithAuthMember(memberId = 1L)
void returnExistingChatRoom() throws Exception {
// given
Long buyerId = 1L;
Expand Down Expand Up @@ -81,6 +83,7 @@ void returnExistingChatRoom() throws Exception {

@Test
@DisplayName("굿즈거래 채팅방 생성 성공 - 기존 채팅방이 없을 경우 새로운 채팅방을 생성한다.")
@WithAuthMember(memberId = 1L)
void createNewChatRoomIfNoneExists() throws Exception {
// given
Long buyerId = 1L;
Expand All @@ -101,7 +104,6 @@ void createNewChatRoomIfNoneExists() throws Exception {

// when & then
mockMvc.perform(post("/api/goods/chat", buyerId)
.param("buyerId", String.valueOf(buyerId))
.param("goodsPostId", String.valueOf(goodsPostId)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("SUCCESS"))
Expand All @@ -120,6 +122,7 @@ void createNewChatRoomIfNoneExists() throws Exception {

@Test
@DisplayName("채팅 내역 조회 성공 - 회원이 채팅방에 참여한 경우 메시지를 페이지로 반환한다.")
@WithAuthMember(memberId = 2L)
void getMessagesForChatRoom_should_return_messages() throws Exception {
// given
Long chatRoomId = 1L;
Expand Down Expand Up @@ -149,7 +152,6 @@ void getMessagesForChatRoom_should_return_messages() throws Exception {

// when & then
mockMvc.perform(get("/api/goods/chat/{chatRoomId}/message", chatRoomId)
.param("memberId", String.valueOf(memberId))
.param("page", "0")
.param("size", "10"))
.andExpect(status().isOk())
Expand Down
Loading

0 comments on commit b179c34

Please sign in to comment.