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,11 +1,13 @@
package io.twogether.nbe_5_7_2_02team.chat.dao;

import io.twogether.nbe_5_7_2_02team.chat.domain.ChatMember;
import io.twogether.nbe_5_7_2_02team.chat.domain.ChatMemberStatus;
import io.twogether.nbe_5_7_2_02team.chat.domain.ChatRoom;
import io.twogether.nbe_5_7_2_02team.member.domain.Member;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Collection;
import java.util.List;

public interface ChatMemberRepository extends JpaRepository<ChatMember, Long> {
Expand All @@ -14,7 +16,8 @@ public interface ChatMemberRepository extends JpaRepository<ChatMember, Long> {

List<ChatMember> findByChatRoom(ChatRoom chatRoom);

List<ChatMember> findByMember(Member member);
List<ChatMember> findByMemberAndChatMemberStatusIn(
Member member, Collection<ChatMemberStatus> chatMemberStatuses);

void deleteByChatRoom(ChatRoom chatRoom);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
Expand All @@ -35,6 +36,7 @@ public class ChatMember extends BaseEntity {
@JoinColumn(name = "member_id")
private Member member;

@Setter
@Column(name = "status")
@Enumerated(EnumType.STRING)
private ChatMemberStatus chatMemberStatus;
Expand All @@ -45,8 +47,4 @@ public ChatMember(ChatRoom chatRoom, Member member, ChatMemberStatus chatMemberS
this.member = member;
this.chatMemberStatus = chatMemberStatus;
}

public void updateStatus(ChatMemberStatus chatMemberStatus) {
this.chatMemberStatus = chatMemberStatus;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package io.twogether.nbe_5_7_2_02team.chat.dto.request;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.twogether.nbe_5_7_2_02team.chat.domain.ChatMemberStatus;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class ChatMemberUpdateRequest {
private final ChatMemberStatus chatMemberStatus;

@JsonCreator
public ChatMemberUpdateRequest(
@JsonProperty("chatMemberStatus") ChatMemberStatus chatMemberStatus) {
this.chatMemberStatus = chatMemberStatus;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package io.twogether.nbe_5_7_2_02team.chat.service;

import static io.twogether.nbe_5_7_2_02team.chat.domain.ChatMemberStatus.LEFT;
import static io.twogether.nbe_5_7_2_02team.chat.domain.ChatMemberStatus.OFFLINE;
import static io.twogether.nbe_5_7_2_02team.chat.domain.ChatMemberStatus.ONLINE;
import static io.twogether.nbe_5_7_2_02team.global.response.error.ErrorCode.CHAT_MEMBER_ALREADY_EXISTS;
import static io.twogether.nbe_5_7_2_02team.global.response.error.ErrorCode.CHAT_MEMBER_NOT_ENTER;
import static io.twogether.nbe_5_7_2_02team.global.response.error.ErrorCode.CHAT_MEMBER_UNDEFINED_STATUS;
import static io.twogether.nbe_5_7_2_02team.global.response.error.ErrorCode.CHAT_ROOM_EMPTY;
Expand All @@ -23,6 +24,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

@Service
Expand All @@ -39,7 +41,9 @@ public List<ChatRoomGetResponse> getChatRoomListByUser(
@AuthenticationPrincipal UserDetails userDetails) {
Member member = checkUserLogin.checkUserLogin(userDetails);

List<ChatMember> chatMemberList = chatMemberRepository.findByMember(member);
List<ChatMember> chatMemberList =
chatMemberRepository.findByMemberAndChatMemberStatusIn(
member, Arrays.asList(ONLINE, OFFLINE));

return chatMemberList.stream()
.map(chatMember -> ChatRoomGetResponse.from(chatMember.getChatRoom()))
Expand Down Expand Up @@ -68,7 +72,11 @@ public Long createChatMember(Long chatroomId, UserDetails userDetails) {
ChatMember chatMember = chatMemberRepository.findByChatRoomAndMember(chatRoom, member);

if (chatMember != null) {
throw new ErrorException(CHAT_MEMBER_ALREADY_EXISTS);
if (chatMember.getChatMemberStatus() == LEFT) {
chatMember.setChatMemberStatus(ONLINE);
}

return chatMember.getId();
}

Long id =
Expand Down Expand Up @@ -105,7 +113,7 @@ public Long updateChatMember(
throw new ErrorException(CHAT_MEMBER_UNDEFINED_STATUS);
}

chatMember.updateStatus(chatMemberStatus);
chatMember.setChatMemberStatus(chatMemberStatus);
return chatMemberRepository.save(chatMember).getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,6 @@ void createChatMemberNoChatRoomTest() {
assertEquals(CHAT_ROOM_NOT_FOUND, errorException.getErrorCode());
}

@Test
@DisplayName("채팅방 입장 테스트: 에러 - 이미 참여중")
void createChatMemberAlreadyJoinTest() {
chatMemberService.createChatMember(chatRoomId, userDetails1);

ErrorException errorException =
assertThrows(
ErrorException.class,
() -> chatMemberService.createChatMember(chatRoomId, userDetails1));

assertEquals(CHAT_MEMBER_ALREADY_EXISTS, errorException.getErrorCode());
}

@Test
@DisplayName("멤버 상태 변경 테스트: 성공")
void updateChatMemberTest() {
Expand Down
69 changes: 59 additions & 10 deletions frontend/src/components/ChatRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,14 @@ function ChatRoom({ chatRoomId, postTitle, onBack }: ChatRoomProps) {

const client = new Client({
webSocketFactory: () => new SockJS(`${import.meta.env.VITE_BASE_URL}/ws/chatroom`),
debug: (str) => {
debug: () => {
},
reconnectDelay: 5000,
connectHeaders: { Authorization: `Bearer ${token}` },
});


client.onConnect = (frame) => {
client.onConnect = () => {
stompClientRef.current = client;
const topic = `/sub/${chatRoomId}/message`;
subscriptionRef.current = client.subscribe(topic, (message: IMessage) => {
Expand Down Expand Up @@ -267,7 +267,7 @@ function ChatRoom({ chatRoomId, postTitle, onBack }: ChatRoomProps) {
client.onStompError = (frame) => {
setError(`STOMP Error: ${frame.headers["message"] || "Connection failed"}`);
};
client.onWebSocketError = (event) => {
client.onWebSocketError = () => {
setError("WebSocket 연결에 실패했습니다. 네트워크 상태를 확인해주세요.");
};
client.onDisconnect = () => {
Expand Down Expand Up @@ -303,17 +303,40 @@ function ChatRoom({ chatRoomId, postTitle, onBack }: ChatRoomProps) {
setShowParticipants((prev) => !prev);
};

const handleChangeParticipantState = (participantId: number) => {
const handleBackButtonPress = () => {
disconnectStomp();
onBack();
};

const handleSelfStatusChange = async (newStatus: string) => {
};
const handleLeaveChatRoom = async () => {
const token = localStorage.getItem("accessToken");
if (!token) {
alert("로그인이 필요합니다.");
return;
}

try {
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/chatroom/${chatRoomId}/member`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ chatMemberStatus: 'LEFT' }),
});

const currentUserParticipant = participants.find((p) => p.id === currentMemberId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: '서버 응답 오류' }));
throw new Error(errorData.message || `채팅방 나가기 실패: ${response.status}`);
}

const handleBackButtonPress = () => {
disconnectStomp();
onBack();
disconnectStomp();
onBack();

} catch (error) {
console.error("Failed to leave chat room:", error);
alert(`채팅방을 나가는 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`);
}
};

return (
Expand Down Expand Up @@ -417,6 +440,32 @@ function ChatRoom({ chatRoomId, postTitle, onBack }: ChatRoomProps) {
<div className="absolute top-0 right-0 w-64 md:w-72 lg:w-80 h-full bg-white/95 backdrop-blur-sm flex flex-col overflow-y-auto transition-all duration-300 ease-in-out shadow-lg z-10 border-l border-[#e4e6eb]">
<div className="p-4 border-b border-[#e4e6eb] flex justify-between items-center bg-gradient-to-r from-[#f0f2f5] to-white">
<h3 className="font-bold text-md text-[#1877f2]">참여중인 멤버</h3>
<button
onClick={() => {
if (window.confirm("채팅방을 나가시겠습니까?")) {
handleLeaveChatRoom();
}
}}
className="bg-transparent border-none cursor-pointer text-black hover:text-gray-600"
aria-label="Leave chat room"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16,17 21,12 16,7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
</button>

</div>

<div className="flex-grow p-2">
Expand Down
Loading