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
@@ -1,7 +1,14 @@
package com.sofa.linkiving.domain.chat.controller;

import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.common.BaseResponse;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

@Tag(name = "Chat", description = "링크 관리 API")
@Tag(name = "Chat", description = "채팅 관리 API")
public interface ChatApi {
@Operation(summary = "채팅방 목록 조회", description = "사용자의 채팅방 목록 정보(채팅방 Id, 제목)을 조회합니다.")
BaseResponse<ChatsRes> getChats(Member member);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
package com.sofa.linkiving.domain.chat.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.facade.ChatFacade;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.global.common.BaseResponse;
import com.sofa.linkiving.security.annotation.AuthMember;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/v1/chat")
@RequestMapping("/v1/chats")
@RequiredArgsConstructor
public class ChatController implements ChatApi {
private final ChatFacade chatFacade;

@Override
@GetMapping
public BaseResponse<ChatsRes> getChats(@AuthMember Member member) {
ChatsRes res = chatFacade.getChats(member);
return BaseResponse.success(res, "채팅방 목록 조회를 성공했습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.sofa.linkiving.domain.chat.dto.response;

import java.util.List;

import com.sofa.linkiving.domain.chat.entity.Chat;

import io.swagger.v3.oas.annotations.media.Schema;

public record ChatsRes(
@Schema(description = "채팅방 목록")
List<ChatSummary> chats
) {

public static ChatsRes from(List<Chat> chatEntities) {
List<ChatSummary> summaries = chatEntities.stream()
.map(ChatSummary::from)
.toList();
return new ChatsRes(summaries);
}

public record ChatSummary(
@Schema(description = "채팅방 Id")
Long id,
@Schema(description = "채팅방 제목")
String title
) {
public static ChatSummary from(Chat chat) {
return new ChatSummary(
chat.getId(),
chat.getTitle()
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.sofa.linkiving.domain.chat.facade;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.service.ChatService;
import com.sofa.linkiving.domain.chat.service.FeedbackService;
import com.sofa.linkiving.domain.chat.service.MessageService;
import com.sofa.linkiving.domain.member.entity.Member;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ChatFacade {
private final ChatService chatService;
private final MessageService messageService;
private final FeedbackService feedbackService;

public ChatsRes getChats(Member member) {
List<Chat> chats = chatService.getChats(member);
return ChatsRes.from(chats);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
package com.sofa.linkiving.domain.chat.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.member.entity.Member;

@Repository
public interface ChatRepository extends JpaRepository<Chat, Long> {
@Query("""
SELECT c
FROM Chat c
JOIN Message m ON m.chat = c
WHERE c.member = :member
GROUP BY c
ORDER BY MAX(m.createdAt) DESC
""")
List<Chat> findAllByMemberOrderByLastMessageDesc(@Param("member") Member member);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package com.sofa.linkiving.domain.chat.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.repository.ChatRepository;
import com.sofa.linkiving.domain.member.entity.Member;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatQueryService {
private final ChatRepository chatRepository;

public List<Chat> findAllOrderByLastMessageDesc(Member member) {
return chatRepository.findAllByMemberOrderByLastMessageDesc(member);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package com.sofa.linkiving.domain.chat.service;

import java.util.List;

import org.springframework.stereotype.Service;

import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.member.entity.Member;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatService {
private final ChatCommandService chatCommandService;
private final ChatQueryService chatQueryService;

public List<Chat> getChats(Member member) {
return chatQueryService.findAllOrderByLastMessageDesc(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.sofa.linkiving.domain.chat.facade;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;

import java.util.List;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.entity.Chat;
import com.sofa.linkiving.domain.chat.service.ChatService;
import com.sofa.linkiving.domain.chat.service.FeedbackService;
import com.sofa.linkiving.domain.chat.service.MessageService;
import com.sofa.linkiving.domain.member.entity.Member;

@ExtendWith(MockitoExtension.class)
public class ChatFacadeTest {
@InjectMocks
private ChatFacade chatFacade;

@Mock
private ChatService chatService;

@Mock
private MessageService messageService;
@Mock
private FeedbackService feedbackService;
@Mock
private Member member;

@Test
@DisplayName("ChatService.getChats 호출 및 ChatsRes 변환 반환")
void shouldReturnChatsResWhenGetChats() {
// given
Chat chat = mock(Chat.class);
given(chat.getId()).willReturn(1L);
given(chat.getTitle()).willReturn("Title");

given(chatService.getChats(member)).willReturn(List.of(chat));

// when
ChatsRes result = chatFacade.getChats(member);

// then
assertThat(result.chats()).hasSize(1);
assertThat(result.chats().get(0).title()).isEqualTo("Title");

verify(chatService).getChats(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.sofa.linkiving.domain.chat.integration;

import static org.mockito.BDDMockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import com.sofa.linkiving.domain.chat.dto.response.ChatsRes;
import com.sofa.linkiving.domain.chat.dto.response.ChatsRes.ChatSummary;
import com.sofa.linkiving.domain.chat.facade.ChatFacade;
import com.sofa.linkiving.domain.member.entity.Member;
import com.sofa.linkiving.domain.member.enums.Role;
import com.sofa.linkiving.domain.member.repository.MemberRepository;
import com.sofa.linkiving.infra.redis.RedisService;
import com.sofa.linkiving.security.userdetails.CustomMemberDetail;

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("test")
public class ChatIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private MemberRepository memberRepository;

@MockitoBean
private ChatFacade chatFacade;
@MockitoBean
private RedisService redisService;

private Member testMember;
private UserDetails testUserDetails;

@BeforeEach
void setUp() {
testMember = memberRepository.save(Member.builder()
.email("list@test.com")
.password("password")
.build());
testUserDetails = new CustomMemberDetail(testMember, Role.USER);
}

@Test
@DisplayName("채팅방 목록 조회 API 호출 및 성공 응답 반환")
void shouldReturnChatListWhenGetChats() throws Exception {
// given
ChatsRes mockResponse = new ChatsRes(List.of(
new ChatSummary(1L, "Chat 1"),
new ChatSummary(2L, "Chat 2")
));

given(chatFacade.getChats(any(Member.class))).willReturn(mockResponse);

// when & then
mockMvc.perform(get("/v1/chats")
.with(user(testUserDetails)))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.data.chats").isArray())
.andExpect(jsonPath("$.data.chats[0].title").value("Chat 1"));
}
}
Loading