Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat #127] 채팅방 목록 API 페이징, 테스트 추가 #128

Merged
merged 9 commits into from
Oct 4, 2024
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.dnd.gongmuin.chat.controller;

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand Down Expand Up @@ -58,11 +56,13 @@ public ResponseEntity<ChatRoomDetailResponse> createChatRoom(

@Operation(summary = "채팅방 목록 조회 API", description = "회원의 채팅방 목록을 조회한다.")
@GetMapping("/api/chat-rooms")
public ResponseEntity<List<ChatRoomSimpleResponse>> getChatRoomsByMember(
public ResponseEntity<PageResponse<ChatRoomSimpleResponse>> getChatRoomsByMember(
@RequestParam("status") String status,
@AuthenticationPrincipal Member member
@AuthenticationPrincipal Member member,
Pageable pageable
) {
List<ChatRoomSimpleResponse> response = chatRoomService.getChatRoomsByMember(member, status);
PageResponse<ChatRoomSimpleResponse> response = chatRoomService.getChatRoomsByMember(member, status,
pageable);
return ResponseEntity.ok(response);
}

Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/dnd/gongmuin/chat/dto/ChatRoomMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import com.dnd.gongmuin.chat.domain.ChatRoom;
import com.dnd.gongmuin.chat.dto.response.AcceptChatResponse;
import com.dnd.gongmuin.chat.dto.response.ChatRoomDetailResponse;
import com.dnd.gongmuin.chat.dto.response.ChatRoomInfo;
import com.dnd.gongmuin.chat.dto.response.ChatRoomSimpleResponse;
import com.dnd.gongmuin.chat.dto.response.LatestChatMessage;
import com.dnd.gongmuin.chat.dto.response.RejectChatResponse;
import com.dnd.gongmuin.member.domain.Member;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
Expand Down Expand Up @@ -59,4 +62,22 @@ public static RejectChatResponse toRejectChatResponse(ChatRoom chatRoom) {
);
}

public static ChatRoomSimpleResponse toChatRoomSimpleResponse(
ChatRoomInfo chatRoomInfo,
LatestChatMessage latestChatMessage
) {
return new ChatRoomSimpleResponse(
chatRoomInfo.chatRoomId(),
new MemberInfo(
chatRoomInfo.partnerId(),
chatRoomInfo.partnerNickname(),
chatRoomInfo.partnerJobGroup(),
chatRoomInfo.partnerProfileImageNo()
),
latestChatMessage.content(),
latestChatMessage.type(),
latestChatMessage.createdAt().toString()
);
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.dnd.gongmuin.chat.repository;

import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import com.dnd.gongmuin.chat.domain.ChatStatus;
import com.dnd.gongmuin.chat.dto.response.ChatRoomInfo;
import com.dnd.gongmuin.member.domain.Member;

public interface ChatRoomQueryRepository {
List<ChatRoomInfo> getChatRoomsByMember(Member member, ChatStatus chatStatus);
Slice<ChatRoomInfo> getChatRoomsByMember(Member member, ChatStatus chatStatus, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;

import com.dnd.gongmuin.chat.domain.ChatStatus;
import com.dnd.gongmuin.chat.dto.response.ChatRoomInfo;
import com.dnd.gongmuin.chat.dto.response.QChatRoomInfo;
Expand All @@ -15,10 +19,15 @@

@RequiredArgsConstructor
public class ChatRoomQueryRepositoryImpl implements ChatRoomQueryRepository {

private final JPAQueryFactory queryFactory;

public List<ChatRoomInfo> getChatRoomsByMember(Member member, ChatStatus chatStatus) {
return queryFactory
public Slice<ChatRoomInfo> getChatRoomsByMember(
Member member,
ChatStatus chatStatus,
Pageable pageable
) {
List<ChatRoomInfo> content = queryFactory
.select(new QChatRoomInfo(
chatRoom.id,
new CaseBuilder()
Expand All @@ -43,5 +52,16 @@ public List<ChatRoomInfo> getChatRoomsByMember(Member member, ChatStatus chatSta
.or(chatRoom.answerer.id.eq(member.getId()))
.and(chatRoom.status.eq(chatStatus)))
.fetch();

boolean hasNext = hasNext(pageable.getPageSize(), content);
return new SliceImpl<>(content, pageable, hasNext);
}

private <T> boolean hasNext(int pageSize, List<T> items) {
if (items.size() <= pageSize) {
return false;
}
items.remove(pageSize);
return true;
}
}
60 changes: 36 additions & 24 deletions src/main/java/com/dnd/gongmuin/chat/service/ChatRoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import com.dnd.gongmuin.member.repository.MemberRepository;
import com.dnd.gongmuin.notification.dto.NotificationEvent;
import com.dnd.gongmuin.question_post.domain.QuestionPost;
import com.dnd.gongmuin.question_post.dto.response.MemberInfo;
import com.dnd.gongmuin.question_post.exception.QuestionPostErrorCode;
import com.dnd.gongmuin.question_post.repository.QuestionPostRepository;

Expand Down Expand Up @@ -90,34 +89,28 @@ public ChatRoomDetailResponse createChatRoom(CreateChatRoomRequest request, Memb
}

@Transactional(readOnly = true)
public List<ChatRoomSimpleResponse> getChatRoomsByMember(Member member, String chatStatus) {
public PageResponse<ChatRoomSimpleResponse> getChatRoomsByMember(Member member, String chatStatus,
Pageable pageable) {
// 회원 채팅방 정보 가져오기
List<ChatRoomInfo> chatRoomInfos = chatRoomRepository.getChatRoomsByMember(member, ChatStatus.from(chatStatus));
Slice<ChatRoomInfo> chatRoomInfos = chatRoomRepository.getChatRoomsByMember(
member, ChatStatus.from(chatStatus), pageable
);

// chatRoomId 리스트 추출
List<Long> chatRoomIds = chatRoomInfos.stream()
.map(ChatRoomInfo::chatRoomId)
.toList();
// 각 채팅방 최근 메시지 정보
List<LatestChatMessage> latestChatMessages = chatMessageQueryRepository.findLatestChatByChatRoomIds(
chatRoomIds);
// <chatRoomId, LatestMessage> -> 순서 보장 x
Map<Long, LatestChatMessage> messageMap = latestChatMessages.stream()
.collect(Collectors.toMap(LatestChatMessage::chatRoomId, message -> message));
// 최신순 정렬
return chatRoomInfos.stream()
.sorted(
Comparator.comparing((ChatRoomInfo info) -> messageMap.get(info.chatRoomId()).createdAt()).reversed())
.map(chatRoomInfo -> {
LatestChatMessage latestMessage = messageMap.get(chatRoomInfo.chatRoomId());
return new ChatRoomSimpleResponse(
chatRoomInfo.chatRoomId(),
new MemberInfo(chatRoomInfo.partnerId(), chatRoomInfo.partnerNickname(),
chatRoomInfo.partnerJobGroup(), chatRoomInfo.partnerProfileImageNo()),
latestMessage.content(),
latestMessage.type(),
latestMessage.createdAt().toString()
);
}).toList();

// 각 채팅방 최근 메시지 가져오기
List<LatestChatMessage> latestChatMessages
= chatMessageQueryRepository.findLatestChatByChatRoomIds(chatRoomIds);

// 두 객체 합쳐서 하나의 DTO로 반환
List<ChatRoomSimpleResponse> responses = getChatRoomSimpleResponses(latestChatMessages,
chatRoomInfos);

// PageResponse 객체 생성
return new PageResponse<>(responses, responses.size(), chatRoomInfos.hasNext());
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -153,6 +146,25 @@ public RejectChatResponse rejectChat(Long chatRoomId, Member member) {
return ChatRoomMapper.toRejectChatResponse(chatRoom);
}

private List<ChatRoomSimpleResponse> getChatRoomSimpleResponses(List<LatestChatMessage> latestChatMessages, Slice<ChatRoomInfo> chatRoomInfos) {
// <chatRoomId, LatestMessage> -> 순서 보장 x
Map<Long, LatestChatMessage> messageMap = latestChatMessages.stream()
.collect(Collectors.toMap(LatestChatMessage::chatRoomId, message -> message));

// 최신순 정렬 및 변환
return chatRoomInfos.stream()
.sorted(
Comparator.comparing(
(ChatRoomInfo info) -> messageMap.get(info.chatRoomId()).createdAt()
).reversed())
.map(chatRoomInfo -> {
LatestChatMessage latestMessage = messageMap.get(chatRoomInfo.chatRoomId());
return ChatRoomMapper.toChatRoomSimpleResponse(
chatRoomInfo, latestMessage
);
}).toList();
}

private ChatRoom getChatRoomById(Long id) {
return chatRoomRepository.findById(id)
.orElseThrow(() -> new NotFoundException(ChatErrorCode.NOT_FOUND_CHAT_ROOM));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void createChatRoom() throws Exception {
.andExpect(status().isOk());
}

@DisplayName("[회원의 채팅방 목록을 조회할 수 있다.]")
@DisplayName("[회원의 요청 상태 채팅방 목록을 조회할 수 있다.]")
@Test
void getChatRoomsByMember() throws Exception {
//given
Expand All @@ -102,7 +102,8 @@ void getChatRoomsByMember() throws Exception {
ChatRoomFixture.chatRoom(questionPosts.get(0), member2, loginMember));
ChatRoom chatRoom3 = chatRoomRepository.save(
ChatRoomFixture.chatRoom(questionPosts.get(1), loginMember, member1));
ChatRoom chatRoom4 = chatRoomRepository.save(ChatRoomFixture.chatRoom(questionPosts.get(1), member2, member1));
ChatRoom unrelatedChatroom = chatRoomRepository.save(
ChatRoomFixture.chatRoom(questionPosts.get(1), member2, member1));
chatMessageRepository.saveAll(
List.of(
chatMessageRepository.save(
Expand All @@ -111,21 +112,33 @@ void getChatRoomsByMember() throws Exception {
ChatMessageFixture.chatMessage(chatRoom1.getId(), "12", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(chatRoom2.getId(), "21", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(chatRoom2.getId(), "22", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(chatRoom3.getId(), "31", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(chatRoom3.getId(), "32", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(chatRoom4.getId(), "41", LocalDateTime.now())),
chatMessageRepository.save(ChatMessageFixture.chatMessage(chatRoom4.getId(), "42", LocalDateTime.now()))
ChatMessageFixture.chatMessage(unrelatedChatroom.getId(), "41", LocalDateTime.now())),
chatMessageRepository.save(
ChatMessageFixture.chatMessage(unrelatedChatroom.getId(), "42", LocalDateTime.now())
)
)
);

// when & then
mockMvc.perform(get("/api/chat-rooms")
.cookie(accessToken)
.param("status", ChatStatus.PENDING.getLabel()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size").value(3))
.andExpect(jsonPath("$.content[0].chatRoomId").value(chatRoom3.getId()))
.andExpect(jsonPath("$.content[0].latestMessage").value("32"))
.andExpect(jsonPath("$.content[0].chatPartner.memberId").value(member1.getId()))
.andExpect(jsonPath("$.content[1].chatRoomId").value(chatRoom2.getId()))
.andExpect(jsonPath("$.content[1].latestMessage").value("21"))
.andExpect(jsonPath("$.content[1].chatPartner.memberId").value(member2.getId()))
.andExpect(jsonPath("$.content[2].chatRoomId").value(chatRoom1.getId()))
.andExpect(jsonPath("$.content[2].latestMessage").value("12"))
.andExpect(jsonPath("$.content[2].chatPartner.memberId").value(member1.getId()))
.andDo(MockMvcResultHandlers.print());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;

import com.dnd.gongmuin.chat.domain.ChatRoom;
import com.dnd.gongmuin.chat.domain.ChatStatus;
Expand All @@ -23,6 +24,8 @@

@DisplayName("[ChatRoomRepository 테스트]")
class ChatRoomRepositoryTest extends DataJpaTestSupport {

private final PageRequest pageRequest = PageRequest.of(0, 10);
@Autowired
ChatRoomRepository chatRoomRepository;
@Autowired
Expand All @@ -44,7 +47,10 @@ void getChatRoomsByMember() {
chatRoomRepository.save(ChatRoomFixture.chatRoom(questionPost, target, answerer))
));
//when
List<ChatRoomInfo> chatRoomInfos = chatRoomRepository.getChatRoomsByMember(target, ChatStatus.PENDING);

List<ChatRoomInfo> chatRoomInfos = chatRoomRepository.getChatRoomsByMember(target, ChatStatus.PENDING,
pageRequest)
.getContent();
//then
Assertions.assertAll(
() -> assertThat(chatRoomInfos).hasSize(2),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

Expand All @@ -25,8 +26,12 @@
import com.dnd.gongmuin.chat.dto.response.AcceptChatResponse;
import com.dnd.gongmuin.chat.dto.response.ChatMessageResponse;
import com.dnd.gongmuin.chat.dto.response.ChatRoomDetailResponse;
import com.dnd.gongmuin.chat.dto.response.ChatRoomInfo;
import com.dnd.gongmuin.chat.dto.response.ChatRoomSimpleResponse;
import com.dnd.gongmuin.chat.dto.response.LatestChatMessage;
import com.dnd.gongmuin.chat.dto.response.RejectChatResponse;
import com.dnd.gongmuin.chat.exception.ChatErrorCode;
import com.dnd.gongmuin.chat.repository.ChatMessageQueryRepository;
import com.dnd.gongmuin.chat.repository.ChatMessageRepository;
import com.dnd.gongmuin.chat.repository.ChatRoomRepository;
import com.dnd.gongmuin.common.exception.runtime.ValidationException;
Expand All @@ -50,6 +55,9 @@ class ChatRoomServiceTest {
@Mock
private ChatMessageRepository chatMessageRepository;

@Mock
private ChatMessageQueryRepository chatMessageQueryRepository;

@Mock
private ChatRoomRepository chatRoomRepository;

Expand Down Expand Up @@ -167,6 +175,42 @@ void createChatRoom_fail() {
.hasMessageContaining(MemberErrorCode.NOT_ENOUGH_CREDIT.getMessage());
}

@DisplayName("[회원이 속한 수락 상태 채팅방 목록을 조회할 수 있다.]")
@Test
void getChatRoomsByMember() {
//given
Long chatRoomId = 1L;
ChatStatus status = ChatStatus.ACCEPTED;
Member targetMember = MemberFixture.member(1L);
Member partner = MemberFixture.member(2L);
ChatRoomInfo chatRoomInfo = new ChatRoomInfo(
chatRoomId, partner.getId(), partner.getNickname(), partner.getJobGroup(), partner.getProfileImageNo()
);
LatestChatMessage latestChatMessage = new LatestChatMessage(
chatRoomId, "와", "텍스트", LocalDateTime.now()
);

given(chatRoomRepository.getChatRoomsByMember(targetMember, status, pageRequest))
.willReturn(new SliceImpl<>(List.of(chatRoomInfo), pageRequest, false));
given(chatMessageQueryRepository.findLatestChatByChatRoomIds(List.of(chatRoomId)))
.willReturn(List.of(latestChatMessage));

//when
List<ChatRoomSimpleResponse> response = chatRoomService.getChatRoomsByMember(
targetMember, status.getLabel(), pageRequest).content();

//then
assertAll(
() -> assertThat(response).hasSize(1),
() -> assertThat(response.get(0).chatRoomId())
.isEqualTo(chatRoomId),
() -> assertThat(response.get(0).chatPartner().memberId())
.isEqualTo(partner.getId()),
() -> assertThat(response.get(0).latestMessage())
.isEqualTo(latestChatMessage.content())
);
}

@DisplayName("[요청자가 채팅방 아이디로 채팅방을 조회할 수 있다.]")
@Test
void getChatRoomById_Inquirer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void registerQuestionPostFail() throws Exception {
.value(MemberErrorCode.NOT_ENOUGH_CREDIT.getCode()));
}

@DisplayName("[질문글을 조회할 수 있다.]")
@DisplayName("[질문글을 아이디로 상세 조회할 수 있다.]")
@Test
void getQuestionPostById() throws Exception {
QuestionPost questionPost = questionPostRepository.save(QuestionPostFixture.questionPost(loginMember));
Expand Down
Loading