From 8fa3862722ae65e5a39af94176da52fd00e9042a Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 14:50:48 +0900 Subject: [PATCH 01/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B4=80=EB=A0=A8=EA=B8=B0=EB=8A=A5,=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=A0=84=EC=86=A1?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9B=B9=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WebsocketConfig.java - configureMessageBroker() 메서드 -> 메시지 브로커 설정 및 메시지 발행, 수신 URI 설정 - registerStompEndpoints() 메서드 -> STOMP 엔드포인트 등록 및 API STOMP 기반 웹소켓 연걸 URI 설정 --- .../boardbuddy/config/WebsocketConfig.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/config/WebsocketConfig.java diff --git a/src/main/java/sumcoda/boardbuddy/config/WebsocketConfig.java b/src/main/java/sumcoda/boardbuddy/config/WebsocketConfig.java new file mode 100644 index 00000000..e4908922 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/config/WebsocketConfig.java @@ -0,0 +1,36 @@ +package sumcoda.boardbuddy.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebsocketConfig implements WebSocketMessageBrokerConfigurer { + + /** + * 메시지 브로커 설정 + * + * @param registry 메시지 브로커 레지스트리 + **/ + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setApplicationDestinationPrefixes("/api/chat"); + + registry.enableSimpleBroker("/api/chat/reception"); + } + + /** + * STOMP 엔드포인트 등록 + * + * @param registry STOMP 엔드포인트 레지스트리 + **/ + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/api/chat/connection") + .setAllowedOriginPatterns("*") + .withSockJS(); + } +} From 63714f34c384370e5799f21d1629d4565f6aa9d7 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 15:05:16 +0900 Subject: [PATCH 02/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A0=88=ED=8F=AC?= =?UTF-8?q?=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B0=8F=20DTO=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatRoom - 사용자의 채팅방 입장을 위한 관련 레포지토리 구현 - ChatRoomRepository.java - findByGatherArticleId(), findById(), existsById() 메서드 명시적으로 선언 - ChatRoomRepositoryCustom.java - findValidateDTOByGatherArticleId(), findChatRoomDetailsListByUsername() 커스텀 메서드 선언 - ChatRoomRepositoryCustomImpl.java - findValidateDTOByGatherArticleId(), findChatRoomDetailsListByUsername() 구현 - 사용자의 채팅방 입장을 위한 ChatRoomResponse DTO 구현 - ChatRoomResponse.InfoDTO.java - 채팅방 정보를 조회하기 위한 DTO - 필드: id, joinedAt, memberChatRoomRole - ChatRoomResponse.ValidateDTO.java - 채팅방이 존재하는지를 조회하기 위한 DTO - 필드: id - ChatRoomResponse.ChatRoomDetailsDTO.java - 참여 채팅방 목록 및 관련 정보를 조회하기 위한 DTO - 필드: chatRoomId, gatherArticleSimpleInfo(해당 채팅방과 관련된 모집글 간단 정보), lastChatMessageInfo(해당 채팅방에서 마지막으로 전송된 메시지 정보) - --- .../boardbuddy/dto/ChatRoomResponse.java | 60 ++++++++++++++ .../chatRoom/ChatRoomRepository.java | 14 ++++ .../chatRoom/ChatRoomRepositoryCustom.java | 15 ++++ .../ChatRoomRepositoryCustomImpl.java | 79 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepository.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustom.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java diff --git a/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java b/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java new file mode 100644 index 00000000..13cb7f36 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java @@ -0,0 +1,60 @@ +package sumcoda.boardbuddy.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import sumcoda.boardbuddy.enumerate.MemberChatRoomRole; + +import java.time.LocalDateTime; + +public class ChatRoomResponse { + + @Getter + @NoArgsConstructor + public static class InfoDTO { + + private Long id; + + private LocalDateTime joinedAt; + + private MemberChatRoomRole memberChatRoomRole; + + @Builder + public InfoDTO(Long id, LocalDateTime joinedAt, MemberChatRoomRole memberChatRoomRole) { + this.id = id; + this.joinedAt = joinedAt; + this.memberChatRoomRole = memberChatRoomRole; + } + } + + @Getter + @NoArgsConstructor + public static class ValidateDTO { + + private Long id; + + @Builder + public ValidateDTO(Long id) { + this.id = id; + } + } + + @Getter + @NoArgsConstructor + public static class ChatRoomDetailsDTO { + + private Long chatRoomId; + + private GatherArticleResponse.SimpleInfoDTO gatherArticleSimpleInfo; + + private ChatMessageResponse.LastChatMessageInfoDTO lastChatMessageInfo; + + @Builder + public ChatRoomDetailsDTO(Long chatRoomId, GatherArticleResponse.SimpleInfoDTO gatherArticleSimpleInfo, ChatMessageResponse.LastChatMessageInfoDTO lastChatMessageInfo) { + this.chatRoomId = chatRoomId; + this.gatherArticleSimpleInfo = gatherArticleSimpleInfo; + this.lastChatMessageInfo = lastChatMessageInfo; + } + } + +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepository.java b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepository.java new file mode 100644 index 00000000..4e3865d7 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepository.java @@ -0,0 +1,14 @@ +package sumcoda.boardbuddy.repository.chatRoom; + +import org.springframework.data.jpa.repository.JpaRepository; +import sumcoda.boardbuddy.entity.ChatRoom; + +import java.util.Optional; + +public interface ChatRoomRepository extends JpaRepository, ChatRoomRepositoryCustom { + Optional findByGatherArticleId(Long gatherArticleId); + + Optional findById(Long chatRoomId); + + boolean existsById(Long chatRoomId); +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustom.java new file mode 100644 index 00000000..5b405b12 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustom.java @@ -0,0 +1,15 @@ +package sumcoda.boardbuddy.repository.chatRoom; + +import org.springframework.stereotype.Repository; +import sumcoda.boardbuddy.dto.ChatRoomResponse; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ChatRoomRepositoryCustom { + + Optional findValidateDTOByGatherArticleId(Long gatherArticleId); + + List findChatRoomDetailsListByUsername(String username); +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java new file mode 100644 index 00000000..8d74a00f --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java @@ -0,0 +1,79 @@ +package sumcoda.boardbuddy.repository.chatRoom; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import sumcoda.boardbuddy.dto.ChatMessageResponse; +import sumcoda.boardbuddy.dto.ChatRoomResponse; +import sumcoda.boardbuddy.dto.GatherArticleResponse; + +import java.util.List; +import java.util.Optional; + +import static sumcoda.boardbuddy.entity.QChatMessage.chatMessage; +import static sumcoda.boardbuddy.entity.QChatRoom.*; +import static sumcoda.boardbuddy.entity.QGatherArticle.gatherArticle; +import static sumcoda.boardbuddy.entity.QMember.member; +import static sumcoda.boardbuddy.entity.QMemberChatRoom.memberChatRoom; + +@RequiredArgsConstructor +public class ChatRoomRepositoryCustomImpl implements ChatRoomRepositoryCustom { + + private final JPAQueryFactory jpaQueryFactory; + + + /** + * 특정 모집글 Id로 ChatRoom 검증 정보 조회 + * + * @param gatherArticleId 모집글 Id + * @return ChatRoom 검증 정보가 포함된 ValidateDTO 객체 + **/ + @Override + public Optional findValidateDTOByGatherArticleId(Long gatherArticleId) { + return Optional.ofNullable(jpaQueryFactory.select(Projections.fields(ChatRoomResponse.ValidateDTO.class, + chatRoom.id)) + .from(chatRoom) + .join(chatRoom.gatherArticle, gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId)) + .fetchOne()); + } + + /** + * 특정 사용자 아이디 사용자가 속한 채팅방 상세 정보 목록 조회 + * + * @param username 사용자 아이디 + * @return 사용자가 속한 채팅방의 상세 정보 목록 + **/ + @Override + public List findChatRoomDetailsListByUsername(String username) { + return jpaQueryFactory + .select(Projections.fields(ChatRoomResponse.ChatRoomDetailsDTO.class, + chatRoom.id, + Projections.constructor(GatherArticleResponse.SimpleInfoDTO.class, + gatherArticle.id, + gatherArticle.title, + gatherArticle.meetingLocation, + gatherArticle.currentParticipants + ).as("gatherArticleSimpleInfo"), + Projections.fields(ChatMessageResponse.LastChatMessageInfoDTO.class, + chatMessage.content, + chatMessage.createdAt.as("sentAt") + ) + )) + .from(chatRoom) + .leftJoin(chatRoom.chatMessages, chatMessage) + .join(chatRoom.gatherArticle, gatherArticle) + .join(chatRoom.memberChatRooms, memberChatRoom) + .join(memberChatRoom.member, member) + .where(member.username.eq(username) + .and(chatMessage.createdAt.eq( + JPAExpressions + .select(chatMessage.createdAt.max()) + .from(chatMessage) + .where(chatMessage.chatRoom.id.eq(chatRoom.id)) + )) + ) + .fetch(); + } +} From ee55eaeb8b94212bec563a8efa5fddf1b6af71a7 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 15:11:40 +0900 Subject: [PATCH 03/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=EC=97=90=20=EC=B0=B8=EC=97=AC=ED=95=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EA=B4=80=EB=A6=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B0=8F=20DTO?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MemberChatRoom - 채팅방에 참여한 사용자의 정보 관리 로직을 구현하기 위한 관련 레포지토리 구현 - MemberChatRoomRepository.java - existsByChatRoomIdAndMemberUsername(), existsByChatRoomIdAndMemberNickname() 메서드 명시적으로 선언 - MemberChatRoomRepositoryCustom.java - existsByGatherArticleIdAndNickname(), findByGatherArticleIdAndUsername() 커스텀 메서드 선언 - MemberChatRoomRepositoryCustomImpl.java - existsByGatherArticleIdAndNickname(), findByGatherArticleIdAndUsername() 구현 - 사용자의 채팅방 입장을 위한 MemberChatRoomResponse DTO 구현 - MemberChatRoomResponse.ValidateDTO.java - 채팅방에 참여한 사용자의 정보를 조회하기 위한 DTO - 필드: id, memberChatRoomRole --- .../dto/MemberChatRoomResponse.java | 24 +++++++ .../MemberChatRoomRepository.java | 13 ++++ .../MemberChatRoomRepositoryCustom.java | 12 ++++ .../MemberChatRoomRepositoryCustomImpl.java | 62 +++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/dto/MemberChatRoomResponse.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepository.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustom.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustomImpl.java diff --git a/src/main/java/sumcoda/boardbuddy/dto/MemberChatRoomResponse.java b/src/main/java/sumcoda/boardbuddy/dto/MemberChatRoomResponse.java new file mode 100644 index 00000000..7700efda --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/dto/MemberChatRoomResponse.java @@ -0,0 +1,24 @@ +package sumcoda.boardbuddy.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import sumcoda.boardbuddy.enumerate.MemberChatRoomRole; + +public class MemberChatRoomResponse { + + @Getter + @NoArgsConstructor + public static class ValidateDTO { + + private Long id; + + private MemberChatRoomRole memberChatRoomRole; + + @Builder + public ValidateDTO(Long id, MemberChatRoomRole memberChatRoomRole) { + this.id = id; + this.memberChatRoomRole = memberChatRoomRole; + } + } +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepository.java b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepository.java new file mode 100644 index 00000000..090aa969 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepository.java @@ -0,0 +1,13 @@ +package sumcoda.boardbuddy.repository.memberChatRoom; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import sumcoda.boardbuddy.entity.MemberChatRoom; + +@Repository +public interface MemberChatRoomRepository extends JpaRepository, MemberChatRoomRepositoryCustom { + + Boolean existsByChatRoomIdAndMemberUsername(Long chatRoomId, String username); + + Boolean existsByChatRoomIdAndMemberNickname(Long chatRoomId, String nickname); +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustom.java new file mode 100644 index 00000000..17e40038 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustom.java @@ -0,0 +1,12 @@ +package sumcoda.boardbuddy.repository.memberChatRoom; + +import sumcoda.boardbuddy.dto.MemberChatRoomResponse; + +import java.util.Optional; + +public interface MemberChatRoomRepositoryCustom { + + Boolean existsByGatherArticleIdAndNickname(Long gatherArticleId, String nickname); + + Optional findByGatherArticleIdAndUsername(Long gatherArticleId, String username); +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustomImpl.java new file mode 100644 index 00000000..2a940d15 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/memberChatRoom/MemberChatRoomRepositoryCustomImpl.java @@ -0,0 +1,62 @@ +package sumcoda.boardbuddy.repository.memberChatRoom; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import sumcoda.boardbuddy.dto.MemberChatRoomResponse; + +import java.util.Optional; + +import static sumcoda.boardbuddy.entity.QChatRoom.chatRoom; +import static sumcoda.boardbuddy.entity.QGatherArticle.gatherArticle; +import static sumcoda.boardbuddy.entity.QMember.member; +import static sumcoda.boardbuddy.entity.QMemberChatRoom.*; + +@RequiredArgsConstructor +public class MemberChatRoomRepositoryCustomImpl implements MemberChatRoomRepositoryCustom { + + private final JPAQueryFactory jpaQueryFactory; + + /** + * 특정 모집글 Id와 닉네임을 가진 사용자가 존재하는지 확인 + * + * @param gatherArticleId 모집글 Id + * @param nickname 사용자 닉네임 + * @return 사용자가 존재하면 true, 아니면 false + **/ + @Override + public Boolean existsByGatherArticleIdAndNickname(Long gatherArticleId, String nickname) { + return jpaQueryFactory + .selectOne() + .from(memberChatRoom) + .leftJoin(memberChatRoom.member, member) + .leftJoin(memberChatRoom.chatRoom, chatRoom) + .leftJoin(chatRoom.gatherArticle, gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId) + .and(member.nickname.eq(nickname))) + .fetchOne() != null; + } + + /** + * 특정 모집글 Id와 사용자 아이디로 MemberChatRoom 정보를 조회 + * + * @param gatherArticleId 모집글 Id + * @param username 사용자 아이디 + * @return MemberChatRoom 정보가 포함된 ValidateDTO 객체 + **/ + @Override + public Optional findByGatherArticleIdAndUsername(Long gatherArticleId, String username) { + return Optional.ofNullable(jpaQueryFactory + .select(Projections.fields(MemberChatRoomResponse.ValidateDTO.class, + memberChatRoom.id, + memberChatRoom.memberChatRoomRole + )) + .from(memberChatRoom) + .leftJoin(memberChatRoom.chatRoom, chatRoom) + .leftJoin(memberChatRoom.member, member) + .leftJoin(chatRoom.gatherArticle, gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId) + .and(member.username.eq(username))) + .fetchOne()); + } +} From 51fc1f9dee01a1260a327ccfe3ba13cb587a92f8 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 15:28:21 +0900 Subject: [PATCH 04/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EB=B0=9C=ED=96=89=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1,=20=EB=A9=94=EC=84=B8=EC=A7=80=20=EC=88=98=EC=8B=A0?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=B0=8F=20DTO=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatMessage - 채팅 메세지 발행 및 전송, 메세지 수신 구현을 위한 관련 레포지토리 구현 - ChatMessageRepository.java - Spring Data JPA 제공 메서드를 활용하기 위한 레포지토리 구현 - ChatMessageRepositoryCustom.java - findMessagesAfterMemberJoinedByChatRoomIdAndUsername(), findTalkMessageById(), findEnterOrExitMessageById() 커스텀 메서드 선언 - ChatMessageRepositoryCustomImpl.java - findMessagesAfterMemberJoinedByChatRoomIdAndUsername(), findTalkMessageById(), findEnterOrExitMessageById() 메서드 구현 - 채팅 메세지 발행 및 전송 구현을 위한 ChatMessageRequest DTO 구현 - ChatMessageRequest.PublishDTO.java - 채팅방에 참여한 사용자들에게 발행할 메세지 내용을 클라이언트로부터 전달받기 위한 DTO - 필드: content - 채팅 메세지 수신 구현을 위한 ChatMessageResponse DTO 구현 - ChatMessageResponse.ChatMessageInfoDTO.java - 입장, 퇴장 메세지를 제외한 일반적인 메세지의 정보를 조회하기 위한 DTO - 필드: content, nickname, profileImageS3SavedURL, rank, messageType, sentAt - ChatMessageResponse.EnterOrExitMessageInfoDTO.java - 입장, 퇴장 메세지의 정보를 조회하기 위한 DTO - 필드: content, messageType - ChatMessageResponse.LatestChatMessageInfoDTO.java - 채팅방에서 가장 최근에 전송된 메시지 내용과 시간을 조회하기 위한 DTO - 필드: content, sentAt --- .../boardbuddy/dto/ChatMessageRequest.java | 20 ++++ .../boardbuddy/dto/ChatMessageResponse.java | 72 ++++++++++++++ .../repository/ChatMessageRepository.java | 11 +++ .../ChatMessageRepositoryCustom.java | 16 +++ .../ChatMessageRepositoryCustomImpl.java | 97 +++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/dto/ChatMessageRequest.java create mode 100644 src/main/java/sumcoda/boardbuddy/dto/ChatMessageResponse.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepository.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustom.java create mode 100644 src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustomImpl.java diff --git a/src/main/java/sumcoda/boardbuddy/dto/ChatMessageRequest.java b/src/main/java/sumcoda/boardbuddy/dto/ChatMessageRequest.java new file mode 100644 index 00000000..9cc5a49f --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/dto/ChatMessageRequest.java @@ -0,0 +1,20 @@ +package sumcoda.boardbuddy.dto; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class ChatMessageRequest { + + @Getter + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class PublishDTO { + private String content; + + @Builder + public PublishDTO(String content) { + this.content = content; + } + } +} diff --git a/src/main/java/sumcoda/boardbuddy/dto/ChatMessageResponse.java b/src/main/java/sumcoda/boardbuddy/dto/ChatMessageResponse.java new file mode 100644 index 00000000..8a7d6e6f --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/dto/ChatMessageResponse.java @@ -0,0 +1,72 @@ +package sumcoda.boardbuddy.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import sumcoda.boardbuddy.enumerate.MessageType; + +import java.time.LocalDateTime; + +public class ChatMessageResponse { + + @Getter + @NoArgsConstructor + public static class ChatMessageInfoDTO { + + private String content; + + private String nickname; + + private String profileImageS3SavedURL; + + private Integer rank; + + private MessageType messageType; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime sentAt; + + @Builder + public ChatMessageInfoDTO(String content, String nickname, String profileImageS3SavedURL, Integer rank, MessageType messageType, LocalDateTime sentAt) { + this.content = content; + this.nickname = nickname; + this.profileImageS3SavedURL = profileImageS3SavedURL; + this.rank = rank; + this.messageType = messageType; + this.sentAt = sentAt; + } + + } + + @Getter + @NoArgsConstructor + public static class EnterOrExitMessageInfoDTO { + + private String content; + + private MessageType messageType; + + @Builder + public EnterOrExitMessageInfoDTO(String content, MessageType messageType) { + this.content = content; + this.messageType = messageType; + } + } + + @Getter + @NoArgsConstructor + public static class LatestChatMessageInfoDTO { + + private String content; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime sentAt; + + @Builder + public LatestChatMessageInfoDTO(String content, LocalDateTime sentAt) { + this.content = content; + this.sentAt = sentAt; + } + } +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepository.java b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepository.java new file mode 100644 index 00000000..0d530c23 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepository.java @@ -0,0 +1,11 @@ +package sumcoda.boardbuddy.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import sumcoda.boardbuddy.entity.ChatMessage; + +@Repository +public interface ChatMessageRepository extends JpaRepository, ChatMessageRepositoryCustom { + + +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustom.java new file mode 100644 index 00000000..9028d742 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustom.java @@ -0,0 +1,16 @@ +package sumcoda.boardbuddy.repository; + +import sumcoda.boardbuddy.dto.ChatMessageResponse; + +import java.util.List; +import java.util.Optional; + +public interface ChatMessageRepositoryCustom { + + List findMessagesAfterMemberJoinedByChatRoomIdAndUsername(Long chatRoomId, String username); + + Optional findTalkMessageById(Long chatMessageId); + + Optional findEnterOrExitMessageById(Long chatMessageId); + +} diff --git a/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustomImpl.java new file mode 100644 index 00000000..63544cab --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/repository/ChatMessageRepositoryCustomImpl.java @@ -0,0 +1,97 @@ +package sumcoda.boardbuddy.repository; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import sumcoda.boardbuddy.dto.ChatMessageResponse; +import sumcoda.boardbuddy.exception.MemberChatRoomRetrievalException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static sumcoda.boardbuddy.entity.QChatMessage.chatMessage; +import static sumcoda.boardbuddy.entity.QChatRoom.chatRoom; +import static sumcoda.boardbuddy.entity.QMember.member; +import static sumcoda.boardbuddy.entity.QMemberChatRoom.*; +import static sumcoda.boardbuddy.entity.QProfileImage.*; + +@RequiredArgsConstructor +public class ChatMessageRepositoryCustomImpl implements ChatMessageRepositoryCustom { + + private final JPAQueryFactory jpaQueryFactory; + + /** + * 사용자가 채팅방에 입장한 이후의 메시지 조회 + * + * @param chatRoomId 채팅방 Id + * @param username 사용자 아이디 + * @return 사용자가 입장한 이후의 채팅방 메시지 목록 + **/ + @Override + public List findMessagesAfterMemberJoinedByChatRoomIdAndUsername(Long chatRoomId, String username) { + + LocalDateTime joinedAt = jpaQueryFactory.select(memberChatRoom.joinedAt) + .from(memberChatRoom) + .where(memberChatRoom.chatRoom.id.eq(chatRoomId) + .and(memberChatRoom.member.username.eq(username))) + .fetchOne(); + + if (joinedAt == null) { + throw new MemberChatRoomRetrievalException("서버 문제로 사용자가 해당 채팅방에 입장한 시간을 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + return jpaQueryFactory.select(Projections.fields(ChatMessageResponse.ChatMessageInfoDTO.class, + chatMessage.content, + member.nickname, + profileImage.profileImageS3SavedURL, + member.rank, + chatMessage.messageType, + chatMessage.createdAt.as("sentAt"))) + .from(chatMessage) + .leftJoin(chatMessage.member, member) + .leftJoin(member.profileImage, profileImage) + .leftJoin(chatMessage.chatRoom, chatRoom) + .where(chatRoom.id.eq(chatRoomId).and(chatMessage.createdAt.after(joinedAt))) + .orderBy(chatMessage.createdAt.asc()) + .fetch(); + } + + /** + * 특정 메시지 ID에 해당하는 대화 메시지 조회 + * + * @param chatMessageId 채팅 메시지 ID + * @return 특정 메시지 ID에 해당하는 대화 메시지 정보 + **/ + @Override + public Optional findTalkMessageById(Long chatMessageId) { + return Optional.ofNullable(jpaQueryFactory.select(Projections.fields(ChatMessageResponse.ChatMessageInfoDTO.class, + chatMessage.content, + member.nickname, + profileImage.profileImageS3SavedURL, + member.rank, + chatMessage.messageType, + chatMessage.createdAt.as("sentAt"))) + .from(chatMessage) + .leftJoin(chatMessage.member, member) + .leftJoin(member.profileImage, profileImage) + .where(chatMessage.id.eq(chatMessageId)) + .fetchOne()); + } + + /** + * 특정 메시지 ID에 해당하는 입장/퇴장 메시지 조회 + * + * @param chatMessageId 채팅 메시지 ID + * @return 특정 메시지 ID에 해당하는 입장/퇴장 메시지 정보 + **/ + @Override + public Optional findEnterOrExitMessageById(Long chatMessageId) { + return Optional.ofNullable(jpaQueryFactory.select(Projections.fields(ChatMessageResponse.EnterOrExitMessageInfoDTO.class, + chatMessage.content, + chatMessage.messageType)) + .from(chatMessage) + .where(chatMessage.id.eq(chatMessageId)) + .fetchOne()); + } +} From d0dee9a1d59c90a8ab02dbd35f7c661c9ba5758e Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 15:44:52 +0900 Subject: [PATCH 05/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=9E=85=EC=9E=A5,=20=ED=87=B4=EC=9E=A5=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B5=AC=ED=98=84=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EB=B9=84=EC=A6=88=EB=8B=88?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatRoom - 채팅방 입장, 퇴장 관련 로직을 구현하기 위한 ChatRoomService.java 구현 - ChatRoomService.java - 필드: chatRoomRepository, memberChatRoomRepository, memberRepository - enterChatRoom() 메서드: 사용자가 특정 모집글과 관련된 채팅방에 입장 - leaveChatRoom() 메서드: 사용자가 특정 모집글과 관련된 채팅방에서 퇴장 - getChatRoomDetailsListByUsername() 메서드: 특정 사용자가 참여하고 있는 채팅방 상세 정보 목록 조회 --- .../boardbuddy/service/ChatRoomService.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/service/ChatRoomService.java diff --git a/src/main/java/sumcoda/boardbuddy/service/ChatRoomService.java b/src/main/java/sumcoda/boardbuddy/service/ChatRoomService.java new file mode 100644 index 00000000..eceaa6fe --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/service/ChatRoomService.java @@ -0,0 +1,122 @@ +package sumcoda.boardbuddy.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sumcoda.boardbuddy.dto.ChatRoomResponse; +import sumcoda.boardbuddy.dto.MemberChatRoomResponse; +import sumcoda.boardbuddy.entity.ChatRoom; +import sumcoda.boardbuddy.entity.Member; +import sumcoda.boardbuddy.entity.MemberChatRoom; +import sumcoda.boardbuddy.enumerate.MemberChatRoomRole; +import sumcoda.boardbuddy.exception.*; +import sumcoda.boardbuddy.exception.member.MemberNotFoundException; +import sumcoda.boardbuddy.exception.member.MemberRetrievalException; +import sumcoda.boardbuddy.repository.chatRoom.ChatRoomRepository; +import sumcoda.boardbuddy.repository.memberChatRoom.MemberChatRoomRepository; +import sumcoda.boardbuddy.repository.MemberRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ChatRoomService { + + private final ChatRoomRepository chatRoomRepository; + + private final MemberChatRoomRepository memberChatRoomRepository; + + private final MemberRepository memberRepository; + + /** + * 사용자가 특정 모집글과 관련된 채팅방에 입장 + * + * @param gatherArticleId 모집글 Id + * @param nickname 사용자 닉네임 + **/ + @Transactional + public Long enterChatRoom(Long gatherArticleId, String nickname) { + ChatRoom chatRoom = chatRoomRepository.findByGatherArticleId(gatherArticleId) + .orElseThrow(() -> new ChatRoomNotFoundException("해당 모집글에 대한 채팅방이 존재하지 않습니다.")); + + Long chatRoomId = chatRoom.getId(); + + if (chatRoomId == null) { + throw new ChatRoomRetrievalException("서버 문제로 해당 채팅방의 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + Member member = memberRepository.findByNickname(nickname) + .orElseThrow(() -> new MemberNotFoundException("해당 사용자를 찾을 수 없습니다.")); + + Boolean isMemberChatRoomExists = memberChatRoomRepository.existsByGatherArticleIdAndNickname(gatherArticleId, nickname); + if (isMemberChatRoomExists) { + throw new AlreadyEnteredChatRoomException("해당 채팅방은 이미 입장한 채팅방입니다."); + } + + MemberChatRoom memberChatRoom = MemberChatRoom.buildMemberChatRoom(LocalDateTime.now(), MemberChatRoomRole.PARTICIPANT, member, chatRoom); + + Long memberChatRoomId = memberChatRoomRepository.save(memberChatRoom).getId(); + + if (memberChatRoomId == null) { + throw new MemberChatRoomSaveException("서버 문제로 채팅방 관련 사용자의 정보를 저장하지 못했습니다. 관리자에게 문의하세요."); + } + + return chatRoomId; + } + + /** + * 사용자가 특정 모집글과 관련된 채팅방에서 퇴장 + * + * @param gatherArticleId 모집글 Id + * @param username 사용자 아이디 + **/ + @Transactional + public Long leaveChatRoom(Long gatherArticleId, String username) { + ChatRoomResponse.ValidateDTO chatRoomValidateDTO = chatRoomRepository.findValidateDTOByGatherArticleId(gatherArticleId) + .orElseThrow(() -> new ChatRoomNotFoundException("해당 모집글에 대한 채팅방이 존재하지 않습니다")); + + Long chatRoomId = chatRoomValidateDTO.getId(); + if (chatRoomId == null) { + throw new ChatRoomRetrievalException("서버문제로 해당 모집글에 대한 채팅방 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + MemberChatRoomResponse.ValidateDTO memberChatRoomValidateDTO = memberChatRoomRepository.findByGatherArticleIdAndUsername(gatherArticleId, username) + .orElseThrow(() -> new MemberChatRoomNotFoundException("채팅방 관련 사용자의 정보를 찾을 수 없습니다.")); + + MemberChatRoomRole memberChatRoomRole = memberChatRoomValidateDTO.getMemberChatRoomRole(); + if (memberChatRoomRole == null) { + throw new MemberChatRoomRetrievalException("서버 문제로 채팅방 관련 사용자의 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + if (memberChatRoomValidateDTO.getMemberChatRoomRole() == MemberChatRoomRole.HOST) { + throw new ChatRoomHostCannotLeaveException("채팅방의 방장은 채팅방을 퇴장할 수 없습니다."); + } + + Long memberChatRoomId = memberChatRoomValidateDTO.getId(); + + if (memberChatRoomId == null) { + throw new MemberChatRoomRetrievalException("서버 문제로 채팅방 관련 사용자의 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + memberChatRoomRepository.deleteById(memberChatRoomId); + + return chatRoomId; + } + + /** + * 특정 사용자가 참여하고 있는 채팅방 상세 정보 목록 조회 + * + * @param username 사용자 아이디 + * @return 사용자가 참여하고 있는 채팅방 상세 정보 목록 + **/ + public List getChatRoomDetailsListByUsername(String username) { + Boolean isMemberExists = memberRepository.existsByUsername(username); + if (!isMemberExists) { + throw new MemberRetrievalException("서버 문제로 사용자의 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + return chatRoomRepository.findChatRoomDetailsListByUsername(username); + } +} \ No newline at end of file From ec84270d5125c803a5246ee9b05c159bf50de488 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 15:56:50 +0900 Subject: [PATCH 06/14] =?UTF-8?q?Feat:=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EB=B0=9C=ED=96=89=20=EB=B0=8F=20=EC=A0=84=EC=86=A1,=20?= =?UTF-8?q?=EC=88=98=EC=8B=A0=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B0=8F=20=EC=9C=A0=ED=8B=B8=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatMessage - 메세지 발행 및 전송, 수신 관련 로직을 구현하기 위한 ChatMessageService.java 구현 - ChatMessageService.java - 필드: chatMessageRepository, chatRoomRepository, memberRepository, memberChatRoomRepository, messagingTemplate - publishMessage() 메서드: 메세지 발행 및 채팅방에 메세지 전송 - publishEnterChatMessage() 메서드: 채팅방 입장 메세지 발행 및 채팅방에 사용자 입장 메세지 전송 - publishExitChatMessage() 메서드: 채팅방 퇴장 메세지 발행 및 채팅방에 사용자 퇴장 메세지 전송 - publishEnterOrExitChatMessage() 메서드: 채팅방 입장/퇴장 메세지 발행 및 채팅방에 사용자 입장/퇴장 메세지 전송 - findMessagesAfterMemberJoinedByChatRoomIdAndUsername() 메서드: 사용자가 채팅방에 입장한 이후의 메세지 조회 - 채팅방 입장/퇴장 메세지를 생성하기 위한 유틸 클래스 구현 - ChatMessageUtil.java - buildChatMessageContent() 메서드: 채팅 메시지 내용을 생성 --- .../service/ChatMessageService.java | 197 ++++++++++++++++++ .../boardbuddy/util/ChatMessageUtil.java | 25 +++ 2 files changed, 222 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/service/ChatMessageService.java create mode 100644 src/main/java/sumcoda/boardbuddy/util/ChatMessageUtil.java diff --git a/src/main/java/sumcoda/boardbuddy/service/ChatMessageService.java b/src/main/java/sumcoda/boardbuddy/service/ChatMessageService.java new file mode 100644 index 00000000..7ada9063 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/service/ChatMessageService.java @@ -0,0 +1,197 @@ +package sumcoda.boardbuddy.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import sumcoda.boardbuddy.dto.ChatMessageRequest; +import sumcoda.boardbuddy.dto.ChatMessageResponse; +import sumcoda.boardbuddy.entity.ChatMessage; +import sumcoda.boardbuddy.entity.ChatRoom; +import sumcoda.boardbuddy.entity.Member; +import sumcoda.boardbuddy.enumerate.MessageType; +import sumcoda.boardbuddy.exception.*; +import sumcoda.boardbuddy.exception.member.MemberNotFoundException; +import sumcoda.boardbuddy.exception.member.MemberRetrievalException; +import sumcoda.boardbuddy.repository.ChatMessageRepository; +import sumcoda.boardbuddy.repository.chatRoom.ChatRoomRepository; +import sumcoda.boardbuddy.repository.MemberRepository; +import sumcoda.boardbuddy.repository.memberChatRoom.MemberChatRoomRepository; +import sumcoda.boardbuddy.util.ChatMessageUtil; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ChatMessageService { + + private final ChatMessageRepository chatMessageRepository; + + private final ChatRoomRepository chatRoomRepository; + + private final MemberRepository memberRepository; + + private final MemberChatRoomRepository memberChatRoomRepository; + + private final SimpMessagingTemplate messagingTemplate; + + /** + * 메세지 발행 및 채팅방에 메세지 전송 + * + * @param chatRoomId 채팅방 Id + * @param publishDTO 전송할 메시지 내용 + * @param username 메세지를 전송하는 사용자 아이디 + **/ + @Transactional + public void publishMessage(Long chatRoomId, ChatMessageRequest.PublishDTO publishDTO, String username) { + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new ChatRoomNotFoundException("해당 채팅방이 존재하지 않습니다.")); + + Member member = memberRepository.findByUsername(username) + .orElseThrow(() -> new MemberNotFoundException("해당 사용자를 찾을 수 없습니다.")); + + Boolean isMemberChatRoomExists = memberChatRoomRepository.existsByChatRoomIdAndMemberUsername(chatRoomId, username); + if (!isMemberChatRoomExists) { + throw new MemberChatRoomRetrievalException("서버 문제로 해당 채팅방의 사용자 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + String content = publishDTO.getContent(); + + ChatMessage chatMessage = ChatMessage.buildChatMessage(content, MessageType.TALK, member, chatRoom); + + Long chatMessageId = chatMessageRepository.save(chatMessage).getId(); + + if (chatMessageId == null) { + throw new ChatMessageSaveException("서버 문제로 메세지를 저장할 수 없습니다. 관리자에게 문의하세요."); + } + + ChatMessageResponse.ChatMessageInfoDTO responseChatMessage = chatMessageRepository.findTalkMessageById(chatMessageId) + .orElseThrow(() -> new ChatMessageRetrievalException("서버 문제로 해당 메세지를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + messagingTemplate.convertAndSend("/api/chat/reception/" + chatRoomId, responseChatMessage); + } + + /** + * 채팅방 입장 메세지 발행 및채팅방에 사용자 입장 메세지 전송 + * + * @param chatRoomId 채팅방 Id + * @param nickname 입장하는 사용자 닉네임 + **/ + @Transactional + public void publishEnterChatMessage(Long chatRoomId, String nickname) { + + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new ChatRoomNotFoundException("해당 채팅방이 존재하지 않습니다.")); + + Member member = memberRepository.findByNickname(nickname) + .orElseThrow(() -> new MemberRetrievalException("해당 유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + Boolean isMemberChatRoomExists = memberChatRoomRepository.existsByChatRoomIdAndMemberNickname(chatRoomId, nickname); + if (!isMemberChatRoomExists) { + throw new MemberChatRoomRetrievalException("서버 문제로 해당 채팅방의 사용자 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + publishEnterOrExitChatMessage(MessageType.ENTER, member, chatRoom); + } + + /** + * 채팅방 퇴장 메세지 발행 및 채팅방에 사용자 퇴장 메세지 전송 + * + * @param chatRoomId 채팅방 Id + * @param username 퇴장하는 사용자 아이디 + **/ + @Transactional + public void publishExitChatMessage(Long chatRoomId, String username) { + + ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new ChatRoomNotFoundException("해당 채팅방이 존재하지 않습니다.")); + + Member member = memberRepository.findByUsername(username) + .orElseThrow(() -> new MemberRetrievalException("해당 유저를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + Boolean isMemberChatRoomExists = memberChatRoomRepository.existsByChatRoomIdAndMemberUsername(chatRoomId, username); + if (!isMemberChatRoomExists) { + throw new MemberChatRoomRetrievalException("서버 문제로 해당 채팅방의 사용자 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + publishEnterOrExitChatMessage(MessageType.EXIT, member, chatRoom); + } + + /** + * 채팅방 입장/퇴장 메세지 발행 및 채팅방에 사용자 입장/퇴장 메세지 전송 + * + * @param messageType 메세지 유형 (입장/퇴장) + * @param member 사용자 정보 + * @param chatRoom 채팅방 정보 + **/ + private void publishEnterOrExitChatMessage(MessageType messageType, Member member, ChatRoom chatRoom) { + + Long chatRoomId = chatRoom.getId(); + + String nickname = member.getNickname(); + + String content = ChatMessageUtil.buildChatMessageContent(nickname, messageType); + + ChatMessage chatMessage = ChatMessage.buildChatMessage(content, messageType, member, chatRoom); + + Long chatMessageId = chatMessageRepository.save(chatMessage).getId(); + + if (chatMessageId == null) { + throw new ChatMessageSaveException("서버 문제로 메세지를 저장할 수 없습니다. 관리자에게 문의하세요."); + } + + ChatMessageResponse.EnterOrExitMessageInfoDTO responseChatMessage = chatMessageRepository.findEnterOrExitMessageById(chatMessageId) + .orElseThrow(() -> new ChatMessageRetrievalException("서버 문제로 해당 메세지를 찾을 수 없습니다. 관리자에게 문의하세요.")); + + // 채팅방 구독자들에게 메시지 전송 + messagingTemplate.convertAndSend("/api/chat/reception/" + chatRoomId, responseChatMessage); + } + + /** + * 사용자가 채팅방에 입장한 이후의 메세지 조회 + * + * @param chatRoomId 채팅방 Id + * @param username 사용자 아이디 + * @return 사용자가 입장한 이후의 채팅방 메시지 목록 + **/ + public List findMessagesAfterMemberJoinedByChatRoomIdAndUsername(Long chatRoomId, String username) { + + boolean isChatRoomExists = chatRoomRepository.existsById(chatRoomId); + + if (!isChatRoomExists) { + throw new ChatRoomNotFoundException("입장하려는 채팅방을 찾을 수 없습니다."); + } + + Boolean isMemberChatRoomExists = memberChatRoomRepository.existsByChatRoomIdAndMemberUsername(chatRoomId, username); + if (isMemberChatRoomExists) { + throw new ChatRoomAccessDeniedException("해당 채팅방에 입장하지 않은 사용자입니다."); + } + + // 채팅방 메시지 조회 로직 + List messages = chatMessageRepository.findMessagesAfterMemberJoinedByChatRoomIdAndUsername(chatRoomId, username); + + if (messages.isEmpty()) { + throw new ChatMessageRetrievalException("서버 문제로 메시지를 조회하지 못하였습니다. 관리자에게 문의하세요"); + } + + return messages.stream() + .map(message -> { + ChatMessageResponse.ChatMessageInfoDTO.ChatMessageInfoDTOBuilder builder = + ChatMessageResponse.ChatMessageInfoDTO.builder() + .content(message.getContent()) + .messageType(message.getMessageType()); + + if (message.getMessageType() == MessageType.TALK) { + builder.nickname(message.getNickname()) + .profileImageS3SavedURL(message.getProfileImageS3SavedURL()) + .rank(message.getRank()) + .sentAt(message.getSentAt()); + } + + return builder.build(); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/util/ChatMessageUtil.java b/src/main/java/sumcoda/boardbuddy/util/ChatMessageUtil.java new file mode 100644 index 00000000..80ba9d0c --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/util/ChatMessageUtil.java @@ -0,0 +1,25 @@ +package sumcoda.boardbuddy.util; + +import sumcoda.boardbuddy.enumerate.MessageType; + +public class ChatMessageUtil { + + /** + * 채팅방 입장/퇴장 메시지 내용을 생성 + * + * @param nickname 사용자 닉네임 + * @param messageType 메시지 유형 (입장/퇴장) + * @return 생성된 채팅 메시지 내용 + **/ + public static String buildChatMessageContent(String nickname, MessageType messageType) { + String content = ""; + + if (messageType.equals(MessageType.ENTER)) { + content = "[입장] " + nickname + "님이 채팅방에 입장했습니다"; + } else if (messageType.equals(MessageType.EXIT)) { + content = "[퇴장] " + nickname + "님이 채팅방에서 퇴장했습니다."; + } + + return content; + } +} From 8392c8edc8b8b752a41012b9c770b3cb8157c0e1 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:11:56 +0900 Subject: [PATCH 07/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=9E=85=EC=9E=A5/=ED=87=B4=EC=9E=A5=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=9D=84=20=EC=BA=90=EC=B9=98=ED=95=98=EA=B8=B0=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatRoom - 채팅방 입장/퇴장 요청을 캐치하기 위한 ChatRoomController.java 구현 - ChatRoomController.java - 필드: chatRoomService, gatherArticleService - getChatRoomGatherArticleInfo() 메서드: 채팅방 정보와 연관된 모집글 정보 조회 요청 캐치 - 요청 URI: '/api/chat/rooms/{chatRoomId}/gather-articles/{gatherArticleId}' - getChatRoomDetailsByUsername() 메서드: 특정 사용자가 참여한 채팅방 목록 조회 요청 캐치 - 요청 URI: '/api/chat/rooms' --- .../controller/ChatRoomController.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/controller/ChatRoomController.java diff --git a/src/main/java/sumcoda/boardbuddy/controller/ChatRoomController.java b/src/main/java/sumcoda/boardbuddy/controller/ChatRoomController.java new file mode 100644 index 00000000..c7601408 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/controller/ChatRoomController.java @@ -0,0 +1,58 @@ +package sumcoda.boardbuddy.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RestController; +import sumcoda.boardbuddy.dto.ChatRoomResponse; +import sumcoda.boardbuddy.dto.GatherArticleResponse; +import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.service.ChatRoomService; +import sumcoda.boardbuddy.service.GatherArticleService; + +import java.util.List; +import java.util.Map; + +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildSuccessResponseWithPairKeyData; + +@RestController +@RequiredArgsConstructor +public class ChatRoomController { + + private final ChatRoomService chatRoomService; + + private final GatherArticleService gatherArticleService; + + /** + * 채팅방 정보와 연관된 모집글 정보 조회 + * + * @param chatRoomId 채팅방 Id + * @param gatherArticleId 모집글 Id + * @return 채팅방과 연관된 모집글 정보 + **/ + @GetMapping("/api/chat/rooms/{chatRoomId}/gather-articles/{gatherArticleId}") + public ResponseEntity>> getChatRoomGatherArticleInfo(@PathVariable Long chatRoomId, + @PathVariable Long gatherArticleId, + @RequestAttribute String username) { + + GatherArticleResponse.SummaryInfoDTO gatherArticleSimpleInfo = gatherArticleService.getChatRoomGatherArticleSimpleInfo(chatRoomId, gatherArticleId, username); + + return buildSuccessResponseWithPairKeyData("gatherArticleSimpleInfo", gatherArticleSimpleInfo, "모집글 정보를 성공적으로 조회했습니다.", HttpStatus.OK); + } + + /** + * 특정 사용자가 참여한 채팅방 목록 조회 + * + * @param username 조회하려는 사용자의 아이디 + * @return 사용자가 참여한 채팅방 목록 + */ + @GetMapping("/api/chat/rooms") + public ResponseEntity>>> getChatRoomDetailsByUsername(@RequestAttribute String username) { + List chatRoomDetailsList = chatRoomService.getChatRoomDetailsListByUsername(username); + + return buildSuccessResponseWithPairKeyData("chatRoomDetailsList", chatRoomDetailsList, "참여중인 채팅방 목록을 성공적으로 조회했습니다.", HttpStatus.OK); + } +} From 8df6ceff11d8613b39182c5ffe55535cf267b80b Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:15:58 +0900 Subject: [PATCH 08/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EB=B0=9C=ED=96=89=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1,=20=EC=B1=84=ED=8C=85=EB=B0=A9=20=EB=A9=94=EC=84=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20=EC=9A=94=EC=B2=AD=EC=9D=84=20?= =?UTF-8?q?=EC=BA=90=EC=B9=98=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatMessage - 채팅방 입장/퇴장 요청을 캐치하기 위한 ChatMessageController.java 구현 - ChatMessageController.java - 필드: chatMessageService - publishMessage() 메서드: 특정 채팅방에 메세지 발행 및 전송 요청 캐치 - 요청 URI: '/api/chat/publication/{chatRoomId}' - getChatMessages() 메서드: 채팅방 메세지 내역 조회 요청 캐치 - 요청 URI: '/api/chat/rooms/{chatRoomId}/messages' --- .../controller/ChatMessageController.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/controller/ChatMessageController.java diff --git a/src/main/java/sumcoda/boardbuddy/controller/ChatMessageController.java b/src/main/java/sumcoda/boardbuddy/controller/ChatMessageController.java new file mode 100644 index 00000000..b5bd0ed8 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/controller/ChatMessageController.java @@ -0,0 +1,61 @@ +package sumcoda.boardbuddy.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestAttribute; +import sumcoda.boardbuddy.dto.ChatMessageRequest; +import sumcoda.boardbuddy.dto.ChatMessageResponse; +import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.service.ChatMessageService; + +import java.util.List; +import java.util.Map; + +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildSuccessResponseWithPairKeyData; + +@Controller +@RequiredArgsConstructor +public class ChatMessageController { + + private final ChatMessageService chatMessageService; + + /** + * 특정 채팅방에 메세지 발행 및 전송 + * + * @param chatRoomId 채팅방 Id + * @param publishDTO 발행할 메세지 내용 DTO + * @param username 메시지를 발행하는 사용자 이름 + **/ + @MessageMapping("/publication/{chatRoomId}") + public void publishMessage( + @DestinationVariable Long chatRoomId, + @Payload ChatMessageRequest.PublishDTO publishDTO, + @RequestAttribute String username) { + + chatMessageService.publishMessage(chatRoomId, publishDTO, username); + } + + /** + * 채팅방 메세지 내역 조회 + * + * @param chatRoomId 채팅방 Id + * @param username 요청을 보낸 사용자 아이디 + * @return 채팅방 메세지 내역 + */ + @GetMapping("/api/chat/rooms/{chatRoomId}/messages") + public ResponseEntity>>> getChatMessages( + @PathVariable Long chatRoomId, + @RequestAttribute String username) { + + List chatMessages = chatMessageService.findMessagesAfterMemberJoinedByChatRoomIdAndUsername(chatRoomId, username); + + return buildSuccessResponseWithPairKeyData("chatMessages", chatMessages, "채팅 메세지들의 정보를 성공적으로 조회했습니다.", HttpStatus.OK); + } +} From 468603370d3d434e19aa2f6f3dd80678f882c63b Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:29:48 +0900 Subject: [PATCH 09/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EC=99=80=20=EC=97=B0=EA=B4=80=EB=90=9C=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=EA=B8=80=20=EC=9A=94=EC=95=BD=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=A1=B0=ED=9D=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20DTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GatherArticleResponse.SummaryInfoDTO.java - 채팅방 정보와 연관된 모집글 요약 정보를 조회하기 위한 DTO - 필드: title, meetingLocation, maxParticipants, currentParticipants, startDateTime, endDateTime - GatherArticleResponse.SimpleInfoDTO.java - 채팅방 정보와 연관된 모집글 간단 정보를 조회하기 위한 DTO - 필드: gatherArticleId, title, maxParticipants, meetingLocation, currentParticipants - GatherArticleRepository.java - existsByChatRoomIdAndId() 메서드 명시적으로 추가 선언 - GatherArticleRepositoryCustom.java - findSimpleInfoByGatherArticleId() 커스텀 메서드 추가 선언 - GatherArticleRepositoryCustomImpl.java - findSimpleInfoByGatherArticleId() 추가된 메서드 구현 - GatherArticleService.java - 채팅방 정보와 연관된 모집글 요약 정보 조희를 위한 메서드 추가 - getChatRoomGatherArticleSimpleInfo() 메서드: 채팅방 정보와 연관된 모집글 간단 정보 조회 --- .../boardbuddy/dto/GatherArticleResponse.java | 53 +++++++++++++++++++ .../GatherArticleRepository.java | 2 + .../GatherArticleRepositoryCustom.java | 2 + .../GatherArticleRepositoryCustomImpl.java | 22 ++++++++ .../service/GatherArticleService.java | 28 ++++++++++ 5 files changed, 107 insertions(+) diff --git a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java index 33ef6afe..387253bf 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/GatherArticleResponse.java @@ -267,4 +267,57 @@ public ReadListDTO(List posts, Boolean last) { this.last = last; } } + + @Getter + @NoArgsConstructor + public static class SummaryInfoDTO { + + private String title; + + private String meetingLocation; + + private Integer maxParticipants; + + private Integer currentParticipants; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime startDateTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime endDateTime; + + @Builder + public SummaryInfoDTO(String title, String meetingLocation, Integer maxParticipants, Integer currentParticipants, LocalDateTime startDateTime, LocalDateTime endDateTime) { + this.title = title; + this.meetingLocation = meetingLocation; + this.maxParticipants = maxParticipants; + this.currentParticipants = currentParticipants; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + } + + @Getter + @NoArgsConstructor + public static class SimpleInfoDTO { + + private Long gatherArticleId; + + private String title; + + private String meetingLocation; + + private Integer currentParticipants; + + @Builder + public SimpleInfoDTO(Long gatherArticleId, String title, String meetingLocation, Integer currentParticipants) { + this.gatherArticleId = gatherArticleId; + this.title = title; + this.meetingLocation = meetingLocation; + this.currentParticipants = currentParticipants; + } + } + + + } diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepository.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepository.java index 9b9fd54f..0d677065 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepository.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepository.java @@ -12,4 +12,6 @@ public interface GatherArticleRepository extends JpaRepository findById(Long gatherArticleId); boolean existsById(Long gatherArticleId); + + Boolean existsByChatRoomIdAndId(Long chatRoomId, Long gatherArticleId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java index 98a070db..4503a364 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustom.java @@ -22,4 +22,6 @@ public interface GatherArticleRepositoryCustom { Slice findReadSliceDTOByLocationAndStatusAndSort( List sidoList, List siguList, List dongList, String status, String sort, Pageable pageable); + + Optional findSimpleInfoByGatherArticleId(Long gatherArticleId); } \ No newline at end of file diff --git a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java index 6a06a431..2b9e8d8c 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/gatherArticle/GatherArticleRepositoryCustomImpl.java @@ -147,6 +147,28 @@ public Slice findReadSliceDTOByLocationAndSt return new SliceImpl<>(results, pageable, hasNext); } + /** + * 특정 모집글 Id로 간단한 모집글 정보 조회 + * + * @param gatherArticleId 모집글 Id + * @return 모집글 정보가 포함된 SummaryInfoDTO 객체 + **/ + @Override + public Optional findSimpleInfoByGatherArticleId(Long gatherArticleId) { + return Optional.ofNullable(jpaQueryFactory + .select(Projections.fields(GatherArticleResponse.SummaryInfoDTO.class, + gatherArticle.title, + gatherArticle.meetingLocation, + gatherArticle.maxParticipants, + gatherArticle.currentParticipants, + gatherArticle.startDateTime, + gatherArticle.endDateTime + )) + .from(gatherArticle) + .where(gatherArticle.id.eq(gatherArticleId)) + .fetchOne()); + } + private BooleanExpression eqStatus(String status) { return status != null ? gatherArticle.gatherArticleStatus.eq(GatherArticleStatus.valueOf(status.toUpperCase())) : null; } diff --git a/src/main/java/sumcoda/boardbuddy/service/GatherArticleService.java b/src/main/java/sumcoda/boardbuddy/service/GatherArticleService.java index a1d4e7c6..87cee5a8 100644 --- a/src/main/java/sumcoda/boardbuddy/service/GatherArticleService.java +++ b/src/main/java/sumcoda/boardbuddy/service/GatherArticleService.java @@ -20,6 +20,7 @@ import sumcoda.boardbuddy.enumerate.GatherArticleStatus; import sumcoda.boardbuddy.exception.gatherArticle.*; import sumcoda.boardbuddy.exception.member.MemberRetrievalException; +import sumcoda.boardbuddy.exception.memberGatherArticle.MemberGatherArticleRetrievalException; import sumcoda.boardbuddy.exception.nearPublicDistrict.NearPublicDistrictRetrievalException; import sumcoda.boardbuddy.exception.publicDistrict.PublicDistrictRetrievalException; import sumcoda.boardbuddy.repository.gatherArticle.GatherArticleRepository; @@ -375,4 +376,31 @@ public GatherArticleResponse.ReadListDTO getGatherArticles(GatherArticleRequest. .last(readSliceDTO.isLast()) .build(); } + + /** + * 채팅방 정보와 연관된 모집글 간단 정보 조회 + * + * @param chatRoomId 채팅방 Id + * @param gatherArticleId 모집글 Id + * @param username 사용자 아이디 + * @return 채팅방과 연관된 모집글 간단 정보 + **/ + public GatherArticleResponse.SummaryInfoDTO getChatRoomGatherArticleSimpleInfo(Long chatRoomId, Long gatherArticleId, String username) { + + // 모집글 정보 조회 + boolean isGatherArticleExists = gatherArticleRepository.existsByChatRoomIdAndId(chatRoomId, gatherArticleId); + + if (isGatherArticleExists) { + throw new GatherArticleNotFoundException("모집글 정보를 찾을 수 없습니다."); + } + + Boolean isMemberGatherArticleExists = memberGatherArticleRepository.existsByGatherArticleIdAndMemberUsername(gatherArticleId, username); + + if (!isMemberGatherArticleExists) { + throw new MemberGatherArticleRetrievalException("서버 문제로 해당 모집글 관련 사용자의 정보를 찾을 수 없습니다. 관리자에게 문의하세요."); + } + + return gatherArticleRepository.findSimpleInfoByGatherArticleId(gatherArticleId) + .orElseThrow(() -> new GatherArticleRetrievalException("서버 문제로 해당 모집글에 대한 정보를 찾을 수 없습니다. 관리자에게 문의하세요.")); + } } From c44755129615faddb3a6c4931d21e36dd77621f8 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:39:04 +0900 Subject: [PATCH 10/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=9E=85=EC=9E=A5/=ED=87=B4=EC=9E=A5=EC=8B=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlreadyEnteredChatRoomException.java - 이미 입장한 채팅방에 입장하려는 경우 - ChatRoomHostCannotLeaveException.java - 채팅방 방장이 채팅방을 나가려는 경우 - ChatRoomNotFoundException.java - 정보를 찾을수 없는 채팅방 정보를 조회하려는 경우 - ChatRoomRetrievalException.java - 서버 문제로 채팅방 정보를 찾을 수 없는 경우 - ChatRoomAccessDeniedException.java - 채팅방에 접근 권한이 없는 사용자인 경우 - ChatRoomExceptionHandler.java - handleChatRoomNotFoundException() 메서드: ChatRoomNotFoundException 등록 - handleChatRoomHostCannotLeaveException() 메서드: ChatRoomHostCannotLeaveException 등록 - handleChatRoomRetrievalException() 메서드: ChatRoomRetrievalException 등록 - handleAlreadyEnteredChatRoomException() 메서드: AlreadyEnteredChatRoomException 등록 - handleChatRoomAccessDeniedException() 메서드: ChatRoomAccessDeniedException 등록 --- .../AlreadyEnteredChatRoomException.java | 7 ++++ .../ChatRoomAccessDeniedException.java | 7 ++++ .../ChatRoomHostCannotLeaveException.java | 7 ++++ .../exception/ChatRoomNotFoundException.java | 7 ++++ .../exception/ChatRoomRetrievalException.java | 7 ++++ .../exception/ChatRoomExceptionHandler.java | 40 +++++++++++++++++++ 6 files changed, 75 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/exception/AlreadyEnteredChatRoomException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatRoomAccessDeniedException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatRoomHostCannotLeaveException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatRoomNotFoundException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatRoomRetrievalException.java create mode 100644 src/main/java/sumcoda/boardbuddy/handler/exception/ChatRoomExceptionHandler.java diff --git a/src/main/java/sumcoda/boardbuddy/exception/AlreadyEnteredChatRoomException.java b/src/main/java/sumcoda/boardbuddy/exception/AlreadyEnteredChatRoomException.java new file mode 100644 index 00000000..315188c8 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/AlreadyEnteredChatRoomException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class AlreadyEnteredChatRoomException extends RuntimeException { + public AlreadyEnteredChatRoomException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatRoomAccessDeniedException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomAccessDeniedException.java new file mode 100644 index 00000000..76201125 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomAccessDeniedException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatRoomAccessDeniedException extends RuntimeException { + public ChatRoomAccessDeniedException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatRoomHostCannotLeaveException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomHostCannotLeaveException.java new file mode 100644 index 00000000..560b2918 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomHostCannotLeaveException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatRoomHostCannotLeaveException extends RuntimeException { + public ChatRoomHostCannotLeaveException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatRoomNotFoundException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomNotFoundException.java new file mode 100644 index 00000000..635b8336 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomNotFoundException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatRoomNotFoundException extends RuntimeException { + public ChatRoomNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatRoomRetrievalException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomRetrievalException.java new file mode 100644 index 00000000..cb2fabf9 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatRoomRetrievalException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatRoomRetrievalException extends RuntimeException { + public ChatRoomRetrievalException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/handler/exception/ChatRoomExceptionHandler.java b/src/main/java/sumcoda/boardbuddy/handler/exception/ChatRoomExceptionHandler.java new file mode 100644 index 00000000..ec1ea75e --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/handler/exception/ChatRoomExceptionHandler.java @@ -0,0 +1,40 @@ +package sumcoda.boardbuddy.handler.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.exception.*; + +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildErrorResponse; +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildFailureResponse; + +@RestControllerAdvice +public class ChatRoomExceptionHandler { + + @ExceptionHandler(ChatRoomNotFoundException.class) + public ResponseEntity> handleChatRoomNotFoundException(ChatRoomNotFoundException e) { + return buildFailureResponse(e.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(ChatRoomHostCannotLeaveException.class) + public ResponseEntity> handleChatRoomHostCannotLeaveException(ChatRoomHostCannotLeaveException e) { + return buildFailureResponse(e.getMessage(), HttpStatus.FORBIDDEN); + } + + @ExceptionHandler(ChatRoomRetrievalException.class) + public ResponseEntity> handleChatRoomRetrievalException(ChatRoomRetrievalException e) { + return buildErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(AlreadyEnteredChatRoomException.class) + public ResponseEntity> handleAlreadyEnteredChatRoomException(AlreadyEnteredChatRoomException e) { + return buildFailureResponse(e.getMessage(), HttpStatus.CONFLICT); + } + + @ExceptionHandler(ChatRoomAccessDeniedException.class) + public ResponseEntity> handleChatRoomAccessDeniedException(ChatRoomAccessDeniedException e) { + return buildFailureResponse(e.getMessage(), HttpStatus.FORBIDDEN); + } +} From 84dafca22ae2e65673a43e2dcf4bcf3364b8da94 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:43:10 +0900 Subject: [PATCH 11/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=EC=97=90=20=EC=9E=85=EC=9E=A5=ED=95=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EA=B4=80=EB=A0=A8=20=EC=A0=95=EB=B3=B4=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=8B=9C=20=EB=B0=9C=EC=83=9D=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EC=98=88=EC=99=B8=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MemberChatRoomNotFoundException.java - 찾을 수 없는, 채팅방에 입장한 사용자의 정보를 요청한 경우 - MemberChatRoomRetrievalException.java - 서버 문제로 채팅방에 입장한 사용자의 정보를 찾을 수 없는 경우 - MemberChatRoomSaveException.java - 서버 문제로 채팅방에 입장한 사용자의 정보를 저장하지 못한 경우 - MemberChatRoomExceptionHandler.java - handleMemberChatRoomNotFoundException() 메서드: MemberChatRoomNotFoundException 등록 - handleMemberChatRoomRetrievalException() 메서드: MemberChatRoomRetrievalException 등록 - handleMemberChatRoomSaveException() 메서드: MemberChatRoomSaveException 등록 --- .../MemberChatRoomNotFoundException.java | 7 ++++ .../MemberChatRoomRetrievalException.java | 7 ++++ .../MemberChatRoomSaveException.java | 7 ++++ .../MemberChatRoomExceptionHandler.java | 32 +++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomNotFoundException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomRetrievalException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomSaveException.java create mode 100644 src/main/java/sumcoda/boardbuddy/handler/exception/MemberChatRoomExceptionHandler.java diff --git a/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomNotFoundException.java b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomNotFoundException.java new file mode 100644 index 00000000..603170a7 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomNotFoundException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class MemberChatRoomNotFoundException extends RuntimeException { + public MemberChatRoomNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomRetrievalException.java b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomRetrievalException.java new file mode 100644 index 00000000..1d75386d --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomRetrievalException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class MemberChatRoomRetrievalException extends RuntimeException { + public MemberChatRoomRetrievalException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomSaveException.java b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomSaveException.java new file mode 100644 index 00000000..4c2d83fb --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/MemberChatRoomSaveException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class MemberChatRoomSaveException extends RuntimeException { + public MemberChatRoomSaveException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/handler/exception/MemberChatRoomExceptionHandler.java b/src/main/java/sumcoda/boardbuddy/handler/exception/MemberChatRoomExceptionHandler.java new file mode 100644 index 00000000..7e8f3402 --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/handler/exception/MemberChatRoomExceptionHandler.java @@ -0,0 +1,32 @@ +package sumcoda.boardbuddy.handler.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.exception.MemberChatRoomNotFoundException; +import sumcoda.boardbuddy.exception.MemberChatRoomRetrievalException; +import sumcoda.boardbuddy.exception.MemberChatRoomSaveException; + +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildErrorResponse; +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildFailureResponse; + +@RestControllerAdvice +public class MemberChatRoomExceptionHandler { + + @ExceptionHandler(MemberChatRoomNotFoundException.class) + public ResponseEntity> handleMemberChatRoomNotFoundException(MemberChatRoomNotFoundException e) { + return buildFailureResponse(e.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(MemberChatRoomRetrievalException.class) + public ResponseEntity> handleMemberChatRoomRetrievalException(MemberChatRoomRetrievalException e) { + return buildErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(MemberChatRoomSaveException.class) + public ResponseEntity> handleMemberChatRoomSaveException(MemberChatRoomSaveException e) { + return buildErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} From 9db6afed15026d782931cbc429e86b67d6e102ba Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:44:48 +0900 Subject: [PATCH 12/14] =?UTF-8?q?Feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=84=B8=EC=A7=80=20=EB=B0=9C=ED=96=89=20=EB=B0=8F=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=EC=8B=9C=20=EB=B0=9C=EC=83=9D=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EC=98=88=EC=99=B8=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatMessageRetrievalException.java - 서버 문제로 채팅 메세지 정보를 찾을 수 없는 경우 - ChatMessageSaveException.java - 서버 문제로 발행한 채팅 메세지 정보를 저장하지 못하는 경우 - ChatMessageExceptionHandler.java - handleChatMessageRetrievalException() 메서드: ChatMessageRetrievalException 등록 - handleChatMessageSaveException() 메서드: ChatMessageSaveException 등록 --- .../ChatMessageRetrievalException.java | 7 ++++++ .../exception/ChatMessageSaveException.java | 7 ++++++ .../ChatMessageExceptionHandler.java | 25 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatMessageRetrievalException.java create mode 100644 src/main/java/sumcoda/boardbuddy/exception/ChatMessageSaveException.java create mode 100644 src/main/java/sumcoda/boardbuddy/handler/exception/ChatMessageExceptionHandler.java diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatMessageRetrievalException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatMessageRetrievalException.java new file mode 100644 index 00000000..283541bb --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatMessageRetrievalException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatMessageRetrievalException extends RuntimeException { + public ChatMessageRetrievalException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/exception/ChatMessageSaveException.java b/src/main/java/sumcoda/boardbuddy/exception/ChatMessageSaveException.java new file mode 100644 index 00000000..b8f9d2fb --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/exception/ChatMessageSaveException.java @@ -0,0 +1,7 @@ +package sumcoda.boardbuddy.exception; + +public class ChatMessageSaveException extends RuntimeException { + public ChatMessageSaveException(String message) { + super(message); + } +} diff --git a/src/main/java/sumcoda/boardbuddy/handler/exception/ChatMessageExceptionHandler.java b/src/main/java/sumcoda/boardbuddy/handler/exception/ChatMessageExceptionHandler.java new file mode 100644 index 00000000..7f9ed7aa --- /dev/null +++ b/src/main/java/sumcoda/boardbuddy/handler/exception/ChatMessageExceptionHandler.java @@ -0,0 +1,25 @@ +package sumcoda.boardbuddy.handler.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.exception.ChatMessageRetrievalException; +import sumcoda.boardbuddy.exception.ChatMessageSaveException; + +import static sumcoda.boardbuddy.builder.ResponseBuilder.buildErrorResponse; + +@RestControllerAdvice +public class ChatMessageExceptionHandler { + + @ExceptionHandler(ChatMessageRetrievalException.class) + public ResponseEntity> handleChatMessageRetrievalException(ChatMessageRetrievalException e) { + return buildErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler(ChatMessageSaveException.class) + public ResponseEntity> handleChatMessageSaveException(ChatMessageSaveException e) { + return buildErrorResponse(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} From b6cfb5d15234fe7281468fecfb830fed655202a3 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 16:50:31 +0900 Subject: [PATCH 13/14] =?UTF-8?q?Refactor:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B8=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=ED=95=84=EB=93=9C=20=EC=9D=B4=EB=A6=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 필드 이름 수정 - MemberChatRoom.java - chatRoomRole -> memberChatRoomRole 로 수정 - ChatRoomRole.java - AUTHOR("author") -> HOST("host") 로 수정 - 클래스 이름 수정으로 인한 리팩토링 - ChatMessageResponse.LastChatMessageInfoDTO -> ChatMessageResponse.LatestChatMessageInfoDTO 로 수정 - ChatRoomRepositoryCustomImpl.java - findChatRoomDetailsListByUsername() 메서드내에 ChatMessageResponse.LastChatMessageInfoDTO -> ChatMessageResponse.LatestChatMessageInfoDTO 로 수정 --- .../sumcoda/boardbuddy/dto/ChatRoomResponse.java | 6 +++--- .../sumcoda/boardbuddy/entity/MemberChatRoom.java | 12 ++++++------ .../{ChatRoomRole.java => MemberChatRoomRole.java} | 4 ++-- .../chatRoom/ChatRoomRepositoryCustomImpl.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/sumcoda/boardbuddy/enumerate/{ChatRoomRole.java => MemberChatRoomRole.java} (79%) diff --git a/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java b/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java index 13cb7f36..5a1b1d92 100644 --- a/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java +++ b/src/main/java/sumcoda/boardbuddy/dto/ChatRoomResponse.java @@ -47,13 +47,13 @@ public static class ChatRoomDetailsDTO { private GatherArticleResponse.SimpleInfoDTO gatherArticleSimpleInfo; - private ChatMessageResponse.LastChatMessageInfoDTO lastChatMessageInfo; + private ChatMessageResponse.LatestChatMessageInfoDTO latestChatMessageInfoDTO; @Builder - public ChatRoomDetailsDTO(Long chatRoomId, GatherArticleResponse.SimpleInfoDTO gatherArticleSimpleInfo, ChatMessageResponse.LastChatMessageInfoDTO lastChatMessageInfo) { + public ChatRoomDetailsDTO(Long chatRoomId, GatherArticleResponse.SimpleInfoDTO gatherArticleSimpleInfo, ChatMessageResponse.LatestChatMessageInfoDTO latestChatMessageInfoDTO) { this.chatRoomId = chatRoomId; this.gatherArticleSimpleInfo = gatherArticleSimpleInfo; - this.lastChatMessageInfo = lastChatMessageInfo; + this.latestChatMessageInfoDTO = latestChatMessageInfoDTO; } } diff --git a/src/main/java/sumcoda/boardbuddy/entity/MemberChatRoom.java b/src/main/java/sumcoda/boardbuddy/entity/MemberChatRoom.java index d6b27a27..a1340558 100644 --- a/src/main/java/sumcoda/boardbuddy/entity/MemberChatRoom.java +++ b/src/main/java/sumcoda/boardbuddy/entity/MemberChatRoom.java @@ -5,7 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import sumcoda.boardbuddy.enumerate.ChatRoomRole; +import sumcoda.boardbuddy.enumerate.MemberChatRoomRole; import java.time.LocalDateTime; @@ -25,7 +25,7 @@ public class MemberChatRoom { // 채팅 참여자의 권한을 나타내기위한 role // ex) AUTHOR, PARTICIPANT @Column(nullable = false) - private ChatRoomRole chatRoomRole; // 역할 추가 (관리자, 일반 사용자 등) + private MemberChatRoomRole memberChatRoomRole; // 역할 추가 (관리자, 일반 사용자 등) // 양방향 연관관계 // 연관관계 주인 @@ -40,18 +40,18 @@ public class MemberChatRoom { private ChatRoom chatRoom; @Builder - public MemberChatRoom(LocalDateTime joinedAt, ChatRoomRole chatRoomRole, Member member, ChatRoom chatRoom) { + public MemberChatRoom(LocalDateTime joinedAt, MemberChatRoomRole memberChatRoomRole, Member member, ChatRoom chatRoom) { this.joinedAt = joinedAt; - this.chatRoomRole = chatRoomRole; + this.memberChatRoomRole = memberChatRoomRole; this.assignMember(member); this.assignChatRoom(chatRoom); } // 직접 빌더 패턴의 생성자를 활용하지 말고 해당 메서드를 활용하여 엔티티 생성 - public static MemberChatRoom buildMemberChatRoom(LocalDateTime joinedAt, ChatRoomRole chatRoomRole, Member member, ChatRoom chatRoom) { + public static MemberChatRoom buildMemberChatRoom(LocalDateTime joinedAt, MemberChatRoomRole memberChatRoomRole, Member member, ChatRoom chatRoom) { return MemberChatRoom.builder() .joinedAt(joinedAt) - .chatRoomRole(chatRoomRole) + .memberChatRoomRole(memberChatRoomRole) .member(member) .chatRoom(chatRoom) .build(); diff --git a/src/main/java/sumcoda/boardbuddy/enumerate/ChatRoomRole.java b/src/main/java/sumcoda/boardbuddy/enumerate/MemberChatRoomRole.java similarity index 79% rename from src/main/java/sumcoda/boardbuddy/enumerate/ChatRoomRole.java rename to src/main/java/sumcoda/boardbuddy/enumerate/MemberChatRoomRole.java index 194c2683..bf9d13f7 100644 --- a/src/main/java/sumcoda/boardbuddy/enumerate/ChatRoomRole.java +++ b/src/main/java/sumcoda/boardbuddy/enumerate/MemberChatRoomRole.java @@ -5,9 +5,9 @@ @Getter @RequiredArgsConstructor -public enum ChatRoomRole { +public enum MemberChatRoomRole { - AUTHOR("author"), + HOST("host"), PARTICIPANT("participant"); private final String value; diff --git a/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java index 8d74a00f..904cf8b7 100644 --- a/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java +++ b/src/main/java/sumcoda/boardbuddy/repository/chatRoom/ChatRoomRepositoryCustomImpl.java @@ -56,7 +56,7 @@ public List findChatRoomDetailsListByUserna gatherArticle.meetingLocation, gatherArticle.currentParticipants ).as("gatherArticleSimpleInfo"), - Projections.fields(ChatMessageResponse.LastChatMessageInfoDTO.class, + Projections.fields(ChatMessageResponse.LatestChatMessageInfoDTO.class, chatMessage.content, chatMessage.createdAt.as("sentAt") ) From 7ac0dee33d7eed5a55b1646bc538d7e35d409717 Mon Sep 17 00:00:00 2001 From: runtime-zer0 Date: Wed, 31 Jul 2024 17:04:44 +0900 Subject: [PATCH 14/14] =?UTF-8?q?Refactor:=20=EC=8A=A4=ED=94=84=EB=A7=81?= =?UTF-8?q?=EC=9D=98=20=EB=8B=A8=EC=9D=BC=20=EC=B1=85=EC=9E=84=20=EC=9B=90?= =?UTF-8?q?=EC=B9=99=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ParticipationApplicationService.java - cancelParticipationApplication() 메서드 - 기존에는 해당 메서드 내에 참가 취소시 이미 참가 승인된 사용자와 참가 신청만 했던 사용자를 구분하여 채팅방 퇴장 메서드를 호출하였으나 스프링의 단일 책임 원칙을 위하여, 채팅방 퇴장 메서드를 분리하여 실행시키기 위한 로직 및 리턴값 수정 - ParticipationApplicationController.java - approveParticipationApplication() 메서드 리팩토링 - 기존의 참가승인 매서드내에서 채팅방 입장 메서드도 함께 호출하였으나 스프링의 단일 책임 원칙을 위하여 컨트롤러 레이어에서 참가신청 승인 메서드와 채팅방 입장/퇴장 메서드를 각각 호출하도록 리팩토링 - cancelParticipationApplication() - 기존의 참가취소 메서드내에서 채팅방 퇴장 메서드도 함께 호출하였으나 스프링의 단일 책임 원칙을 위하여 컨트롤러 레이어에서 참가신청 취소 메서드와 채팅방 퇴장 메서드 호출을 구분하도록 리팩토링 - 채팅방 퇴장 로직은 이미 참가 승인되어 채팅방에 입장한 사람만 채팅방에서 퇴장해야하므로 참가 승인되어 채팅방에 입장한 사용자와 참가 신청만한 사용자를 구분하기 위해 참가 취소 메서드의 리턴값을 리팩토링하여 값을 받은후 해당 값을 바탕으로 참가 승인된 사용자만 채팅방 퇴장 메서드를 호출하도록 리팩토링 --- .../ParticipationApplicationController.java | 28 ++++++++++++++++++- .../ParticipationApplicationService.java | 7 ++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/sumcoda/boardbuddy/controller/ParticipationApplicationController.java b/src/main/java/sumcoda/boardbuddy/controller/ParticipationApplicationController.java index bfe154fc..822264ce 100644 --- a/src/main/java/sumcoda/boardbuddy/controller/ParticipationApplicationController.java +++ b/src/main/java/sumcoda/boardbuddy/controller/ParticipationApplicationController.java @@ -6,6 +6,8 @@ import org.springframework.web.bind.annotation.*; import sumcoda.boardbuddy.dto.ParticipationApplicationResponse; import sumcoda.boardbuddy.dto.common.ApiResponse; +import sumcoda.boardbuddy.service.ChatMessageService; +import sumcoda.boardbuddy.service.ChatRoomService; import sumcoda.boardbuddy.service.ParticipationApplicationService; import java.util.List; @@ -19,6 +21,10 @@ public class ParticipationApplicationController { private final ParticipationApplicationService participationApplicationService; + private final ChatRoomService chatRoomService; + + private final ChatMessageService chatMessageService; + /** * 모집글 참가 신청 요청 * @@ -42,8 +48,15 @@ public ResponseEntity> applyParticipation(@PathVariable Long g **/ @PutMapping("/api/gather-articles/{gatherArticleId}/participation/{participationApplicationId}/approval") public ResponseEntity> approveParticipationApplication(@PathVariable Long gatherArticleId, @PathVariable Long participationApplicationId, @RequestAttribute String username, @RequestParam String applicantNickname) { + participationApplicationService.approveParticipationApplication(gatherArticleId, participationApplicationId, username); + // ChatRoom 입장 처리 + Long chatRoomId = chatRoomService.enterChatRoom(gatherArticleId, applicantNickname); + + // 채팅방 입장 메세지 발행 및 전송 + chatMessageService.publishEnterChatMessage(chatRoomId, applicantNickname); + return buildSuccessResponseWithoutData(applicantNickname + "님의 참가 신청을 승인 했습니다.", HttpStatus.OK); } @@ -57,6 +70,7 @@ public ResponseEntity> approveParticipationApplication(@PathVa **/ @PutMapping("/api/gather-articles/{gatherArticleId}/participation/{participationApplicationId}/rejection") public ResponseEntity> rejectParticipationApplication(@PathVariable Long gatherArticleId, @PathVariable Long participationApplicationId, @RequestAttribute String username, @RequestParam String applicantNickname) { + participationApplicationService.rejectParticipationApplication(gatherArticleId, participationApplicationId, username); return buildSuccessResponseWithoutData(applicantNickname + "님의 참가 신청을 거절 했습니다.", HttpStatus.OK); @@ -70,7 +84,17 @@ public ResponseEntity> rejectParticipationApplication(@PathVar **/ @PutMapping("/api/gather-articles/{gatherArticleId}/participation") public ResponseEntity> cancelParticipationApplication(@PathVariable Long gatherArticleId, @RequestAttribute String username) { - participationApplicationService.cancelParticipationApplication(gatherArticleId, username); + + Boolean isMemberParticipant = participationApplicationService.cancelParticipationApplication(gatherArticleId, username); + + // 만약 참가 취소하는 사용자가 참가 승인으로 인하여, 모집글에 참여중인 사용자라면, + if (isMemberParticipant) { + // ChatRoom 퇴장 처리 + Long chatRoomId = chatRoomService.leaveChatRoom(gatherArticleId, username); + + // 채팅방 퇴장 메세지 발행 및 전송 + chatMessageService.publishExitChatMessage(chatRoomId, username); + } return buildSuccessResponseWithoutData("해당 모집글의 참가 신청을 취소했습니다.", HttpStatus.OK); } @@ -84,7 +108,9 @@ public ResponseEntity> cancelParticipationApplication(@PathVar **/ @GetMapping("/api/gather-articles/{gatherArticleId}/participation") public ResponseEntity>>> getParticipationAppliedMemberList(@PathVariable Long gatherArticleId, @RequestAttribute String username) { + List participationAppliedMemberList = participationApplicationService.getParticipationAppliedMemberList(gatherArticleId, username); + return buildSuccessResponseWithPairKeyData("participationAppliedMemberList", participationAppliedMemberList, "해당 모집글의 참가 신청 목록을 성공적으로 조회했습니다.", HttpStatus.OK); } } diff --git a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java index ebcf23f0..eb395be9 100644 --- a/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java +++ b/src/main/java/sumcoda/boardbuddy/service/ParticipationApplicationService.java @@ -228,7 +228,7 @@ public void rejectParticipationApplication(Long gatherArticleId, Long participat * @param username 참가신청을 취소하는 사용자 아이디 **/ @Transactional - public void cancelParticipationApplication(Long gatherArticleId, String username) { + public Boolean cancelParticipationApplication(Long gatherArticleId, String username) { GatherArticle gatherArticle = gatherArticleRepository.findById(gatherArticleId) .orElseThrow(() -> new GatherArticleNotFoundException("해당 모집글이 존재하지 않습니다.")); @@ -258,12 +258,17 @@ public void cancelParticipationApplication(Long gatherArticleId, String username // 참가 신청 취소 처리 participationApplication.assignParticipationApplicationStatus(ParticipationApplicationStatus.CANCELED); + boolean isMemberParticipant = false; + if (memberGatherArticle.getMemberGatherArticleRole() == MemberGatherArticleRole.PARTICIPANT) { + isMemberParticipant = true; memberGatherArticle.assignMemberGatherArticleRole(MemberGatherArticleRole.NONE); } // 모집글의 현재 참가자 수 업데이트 gatherArticle.assignCurrentParticipants(gatherArticle.getCurrentParticipants() - 1); + + return isMemberParticipant; } /**