diff --git a/src/main/java/com/gamegoo/controller/chat/ChatController.java b/src/main/java/com/gamegoo/controller/chat/ChatController.java index 6aff4c5b..9da9723f 100644 --- a/src/main/java/com/gamegoo/controller/chat/ChatController.java +++ b/src/main/java/com/gamegoo/controller/chat/ChatController.java @@ -141,6 +141,14 @@ public ApiResponse exitChatroom( return ApiResponse.onSuccess("채팅방 나가기 성공"); } + @Operation(summary = "안읽은 채팅방 uuid 목록 조회 API", description = "안읽은 메시지가 속한 채팅방의 uuid 목록을 조회하는 API 입니다.") + @GetMapping("/chat/unread") + public ApiResponse> getUnreadChatroomUuid() { + Long memberId = JWTUtil.getCurrentUserId(); + List chatroomUuids = chatQueryService.getUnreadChatroomUuids(memberId); + return ApiResponse.onSuccess(chatroomUuids); + } + @Operation(summary = "매칭을 통한 채팅방 시작 메소드 테스트용 API", description = "매칭을 통한 채팅방 시작 메소드를 테스트하기 위한 API 입니다.\n\n" + "대상 회원과의 채팅방이 이미 존재하는 경우, 해당 채팅방 uuid를 리턴합니다.\n\n" + @@ -149,7 +157,6 @@ public ApiResponse exitChatroom( @Parameter(name = "memberId1", description = "매칭 시켜줄 회원 id 입니다."), @Parameter(name = "memberId2", description = "매칭 시켜줄 회원 id 입니다.") }) - @GetMapping("/chat/start/matching/{memberId1}/{memberId2}") public ApiResponse startChatroomByMatching( @PathVariable(name = "memberId1") Long memberId1, diff --git a/src/main/java/com/gamegoo/converter/ChatConverter.java b/src/main/java/com/gamegoo/converter/ChatConverter.java index e83552f7..67398329 100644 --- a/src/main/java/com/gamegoo/converter/ChatConverter.java +++ b/src/main/java/com/gamegoo/converter/ChatConverter.java @@ -64,5 +64,4 @@ public static ChatResponse.SystemMessageDTO toSystemMessageDTO(Chat chat) { .boardId(chat.getSourceBoard() != null ? chat.getSourceBoard().getId() : null) .build(); } - } diff --git a/src/main/java/com/gamegoo/service/board/BoardService.java b/src/main/java/com/gamegoo/service/board/BoardService.java index 5e451f2f..ae61df7a 100644 --- a/src/main/java/com/gamegoo/service/board/BoardService.java +++ b/src/main/java/com/gamegoo/service/board/BoardService.java @@ -20,7 +20,10 @@ import com.gamegoo.service.manner.MannerService; import com.gamegoo.service.member.FriendService; import com.gamegoo.util.MemberUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -336,9 +339,11 @@ public BoardResponse.boardByIdResponseForMemberDTO getBoardByIdForMember(Long bo Member poster = board.getMember(); - List mannerKeywordDTOs = mannerService.mannerKeyword(poster); + List mannerKeywordDTOs = mannerService.mannerKeyword( + poster); - List mannerKeywords = mannerService.sortMannerKeywordDTOs(mannerKeywordDTOs); + List mannerKeywords = mannerService.sortMannerKeywordDTOs( + mannerKeywordDTOs); return BoardResponse.boardByIdResponseForMemberDTO.builder() .boardId(board.getId()) @@ -399,4 +404,15 @@ public List getMyBoardList(Long memberId, .build(); }).collect(Collectors.toList()); } + + /** + * boardId로 board 엔티티 조회 + * + * @param boardId + * @return + */ + public Board findBoard(Long boardId) { + return boardRepository.findById(boardId) + .orElseThrow(() -> new BoardHandler(ErrorStatus.BOARD_NOT_FOUND)); + } } diff --git a/src/main/java/com/gamegoo/service/chat/ChatCommandService.java b/src/main/java/com/gamegoo/service/chat/ChatCommandService.java index b5c71e74..eaadc892 100644 --- a/src/main/java/com/gamegoo/service/chat/ChatCommandService.java +++ b/src/main/java/com/gamegoo/service/chat/ChatCommandService.java @@ -1,7 +1,6 @@ package com.gamegoo.service.chat; import com.gamegoo.apiPayload.code.status.ErrorStatus; -import com.gamegoo.apiPayload.exception.handler.BoardHandler; import com.gamegoo.apiPayload.exception.handler.ChatHandler; import com.gamegoo.converter.ChatConverter; import com.gamegoo.domain.board.Board; @@ -18,6 +17,7 @@ import com.gamegoo.repository.chat.ChatroomRepository; import com.gamegoo.repository.chat.MemberChatroomRepository; import com.gamegoo.repository.member.MemberRepository; +import com.gamegoo.service.board.BoardService; import com.gamegoo.service.member.FriendService; import com.gamegoo.service.member.ProfileService; import com.gamegoo.service.socket.SocketService; @@ -38,6 +38,7 @@ public class ChatCommandService { private final ProfileService profileService; private final FriendService friendService; private final SocketService socketService; + private final BoardService boardService; private final MemberRepository memberRepository; private final MemberChatroomRepository memberChatroomRepository; private final ChatroomRepository chatroomRepository; @@ -59,110 +60,42 @@ public class ChatCommandService { */ public ChatResponse.ChatroomEnterDTO startChatroomByMemberId(Long memberId, Long targetMemberId) { + + // 대상 회원 검증 및 에러 처리 + MemberUtils.validateDifferentMembers(memberId, targetMemberId, + ErrorStatus.CHAT_TARGET_MEMBER_ID_INVALID); + Member member = profileService.findMember(memberId); // 채팅 대상 회원의 존재 여부 검증 Member targetMember = memberRepository.findById(targetMemberId) .orElseThrow(() -> new ChatHandler(ErrorStatus.CHAT_TARGET_NOT_FOUND)); - // 채팅 대상으로 자기 자신을 요청한 경우 - if (member.equals(targetMember)) { - throw new ChatHandler(ErrorStatus.CHAT_TARGET_MEMBER_ID_INVALID); - } - // 채팅 대상 회원이 탈퇴한 경우 MemberUtils.checkBlind(targetMember); // 내가 채팅 대상 회원을 차단한 경우, 기존 채팅방 입장 및 새 채팅방 생성 모두 불가 - if (MemberUtils.isBlocked(member, targetMember)) { - throw new ChatHandler(ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); - } - - // 채팅 대상 회원과의 chatroom 존재 여부 조회 - Optional chatroom = chatroomRepository.findChatroomByMemberIds( - member.getId(), targetMember.getId()); - - // 기존에 채팅방이 존재하는 경우: 메시지 내역, 상대 회원 정보 조회 및 해당 채팅방 입장 처리 - if (chatroom.isPresent()) { - MemberChatroom memberChatroom = memberChatroomRepository.findByMemberIdAndChatroomId( - member.getId(), chatroom.get().getId()) - .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); - - // 채팅 대상 회원이 나를 차단함 && 내가 해당 채팅방을 퇴장한 상태인 경우 - if (MemberUtils.isBlocked(targetMember, member) - && memberChatroom.getLastJoinDate() == null) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); - } - - // 최근 메시지 내역 조회 - Slice recentChats = chatRepository.findRecentChats(chatroom.get().getId(), - memberChatroom.getId(), member.getId()); - - // 해당 채팅방의 lastViewDate 업데이트 - memberChatroom.updateLastViewDate(LocalDateTime.now()); - - // ChatMessageListDTO 생성 - ChatResponse.ChatMessageListDTO chatMessageListDTO = ChatConverter.toChatMessageListDTO( - recentChats); - - return ChatroomEnterDTO.builder() - .uuid(chatroom.get().getUuid()) - .memberId(targetMember.getId()) - .gameName(targetMember.getGameName()) - .memberProfileImg(targetMember.getProfileImage()) - .friend(friendService.isFriend(member, targetMember)) - .blocked(MemberUtils.isBlocked(targetMember, member)) - .system(null) - .chatMessageList(chatMessageListDTO) - .build(); - } else { - // 기존에 채팅방이 존재하지 않는 경우: 새 채팅방 생성 - - // 채팅 상대 회원이 나를 차단한 경우, 새 채팅방 생성 불가 - if (MemberUtils.isBlocked(targetMember, member)) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); - } - - // chatroom 엔티티 생성 - String uuid = UUID.randomUUID().toString(); - Chatroom newChatroom = Chatroom.builder() - .uuid(uuid) - .startMember(member) - .build(); - - Chatroom savedChatroom = chatroomRepository.save(newChatroom); - - // MemberChatroom 엔티티 생성 및 연관관계 매핑 - // 나의 MemberChatroom 엔티티 - MemberChatroom memberChatroom = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(null) - .chatroom(savedChatroom) - .build(); - memberChatroom.setMember(member); - memberChatroomRepository.save(memberChatroom); - - // 상대방의 MemberChatroom 엔티티 - MemberChatroom targetMemberChatroom = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(null) - .chatroom(savedChatroom) - .build(); - targetMemberChatroom.setMember(targetMember); - memberChatroomRepository.save(targetMemberChatroom); - - return ChatroomEnterDTO.builder() - .uuid(savedChatroom.getUuid()) - .memberId(targetMember.getId()) - .gameName(targetMember.getGameName()) - .memberProfileImg(targetMember.getProfileImage()) - .friend(friendService.isFriend(member, targetMember)) - .blocked(false) - .system(null) - .chatMessageList(null) - .build(); - } - + MemberUtils.validateBlocked(member, targetMember, + ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); + + return chatroomRepository.findChatroomByMemberIds( + member.getId(), targetMember.getId()) + .map(existingChatroom -> enterExistingChatroom(member, targetMember, + existingChatroom, null))// 기존 채팅방 존재하는 경우, 해당 채팅방에 입장 + .orElseGet(() -> { + // 기존에 채팅방이 존재하지 않는 경우: 새 채팅방 생성 + Chatroom newChatroom = createNewChatroom(member, targetMember, null); + return ChatroomEnterDTO.builder() + .uuid(newChatroom.getUuid()) + .memberId(targetMember.getId()) + .gameName(targetMember.getGameName()) + .memberProfileImg(targetMember.getProfileImage()) + .friend(friendService.isFriend(member, targetMember)) + .blocked(false) + .system(null) + .chatMessageList(null) + .build(); + }); } /** @@ -176,128 +109,48 @@ public ChatResponse.ChatroomEnterDTO startChatroomByBoardId(Long memberId, Long Member member = profileService.findMember(memberId); // 게시글 엔티티 조회 - Board board = boardRepository.findById(boardId) - .orElseThrow(() -> new BoardHandler(ErrorStatus.BOARD_NOT_FOUND)); + Board board = boardService.findBoard(boardId); // 채팅 대상 회원의 존재 여부 검증 Member targetMember = memberRepository.findById(board.getMember().getId()) .orElseThrow(() -> new ChatHandler(ErrorStatus.CHAT_TARGET_NOT_FOUND)); + // 게시글 작성자가 본인인 경우 + MemberUtils.validateDifferentMembers(member.getId(), targetMember.getId(), + ErrorStatus.CHAT_TARGET_MEMBER_ID_INVALID); + // 채팅 대상 회원이 탈퇴한 경우 MemberUtils.checkBlind(targetMember); // 내가 채팅 대상 회원을 차단한 경우, 기존 채팅방 입장 및 새 채팅방 생성 모두 불가 - if (MemberUtils.isBlocked(member, targetMember)) { - throw new ChatHandler(ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); - } - - // 채팅 대상 회원과의 chatroom 존재 여부 조회 - Optional chatroom = chatroomRepository.findChatroomByMemberIds( - member.getId(), targetMember.getId()); - - // 기존에 채팅방이 존재하는 경우: 메시지 내역, 상대 회원 정보 조회 및 해당 채팅방 입장 처리 - if (chatroom.isPresent()) { - MemberChatroom memberChatroom = memberChatroomRepository.findByMemberIdAndChatroomId( - member.getId(), chatroom.get().getId()) - .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); - - // 채팅 대상 회원이 나를 차단함 && 내가 해당 채팅방을 퇴장한 상태인 경우 - if (MemberUtils.isBlocked(targetMember, member) - && memberChatroom.getLastJoinDate() == null) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); - } - - // 최근 메시지 내역 조회 - Slice recentChats = chatRepository.findRecentChats(chatroom.get().getId(), - memberChatroom.getId(), member.getId()); - - // 해당 채팅방의 lastViewDate 업데이트 - memberChatroom.updateLastViewDate(LocalDateTime.now()); - - // ChatMessageListDTO 생성 - ChatResponse.ChatMessageListDTO chatMessageListDTO = ChatConverter.toChatMessageListDTO( - recentChats); - - // 시스템 메시지 기능을 위한 SystemFlagDTO 생성 - ChatResponse.SystemFlagDTO systemFlagDTO = memberChatroom.getLastJoinDate() == null - ? ChatResponse.SystemFlagDTO.builder() - .flag(1) - .boardId(boardId) - .build() - : ChatResponse.SystemFlagDTO.builder() - .flag(2) + MemberUtils.validateBlocked(member, targetMember, + ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); + + return chatroomRepository.findChatroomByMemberIds( + member.getId(), targetMember.getId()) + .map(exitChatroom -> enterExistingChatroom(member, targetMember, + exitChatroom, board.getId())) // 기존 채팅방 존재하는 경우, 해당 채팅방에 입장 및 system 값 포함 + .orElseGet(() -> { + // 기존에 채팅방이 존재하지 않는 경우: 새 채팅방 생성 + + Chatroom newChatroom = createNewChatroom(member, targetMember, null); + // 시스템 메시지 기능을 위한 SystemFlagDTO 생성 + ChatResponse.SystemFlagDTO systemFlagDTO = ChatResponse.SystemFlagDTO.builder() + .flag(1) .boardId(boardId) .build(); - return ChatroomEnterDTO.builder() - .uuid(chatroom.get().getUuid()) - .memberId(targetMember.getId()) - .gameName(targetMember.getGameName()) - .memberProfileImg(targetMember.getProfileImage()) - .friend(friendService.isFriend(member, targetMember)) - .blocked(MemberUtils.isBlocked(targetMember, member)) - .system(systemFlagDTO) - .chatMessageList(chatMessageListDTO) - .build(); - } else { - // 기존에 채팅방이 존재하지 않는 경우: 새 채팅방 생성 - - // 채팅 상대 회원이 나를 차단한 경우, 새 채팅방 생성 불가 - if (MemberUtils.isBlocked(targetMember, member)) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); - } - - // chatroom 엔티티 생성 - String uuid = UUID.randomUUID().toString(); - Chatroom newChatroom = Chatroom.builder() - .uuid(uuid) - .startMember(member) - .build(); - - Chatroom savedChatroom = chatroomRepository.save(newChatroom); - - // MemberChatroom 엔티티 생성 및 연관관계 매핑 - // 나의 MemberChatroom 엔티티 - MemberChatroom memberChatroom = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(null) - .chatroom(savedChatroom) - .build(); - memberChatroom.setMember(member); - memberChatroomRepository.save(memberChatroom); - - // 상대방의 MemberChatroom 엔티티 - MemberChatroom targetMemberChatroom = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(null) - .chatroom(savedChatroom) - .build(); - targetMemberChatroom.setMember(targetMember); - memberChatroomRepository.save(targetMemberChatroom); - - // 시스템 메시지 기능을 위한 SystemFlagDTO 생성 - ChatResponse.SystemFlagDTO systemFlagDTO = memberChatroom.getLastJoinDate() == null - ? ChatResponse.SystemFlagDTO.builder() - .flag(1) - .boardId(boardId) - .build() - : ChatResponse.SystemFlagDTO.builder() - .flag(2) - .boardId(boardId) + return ChatroomEnterDTO.builder() + .uuid(newChatroom.getUuid()) + .memberId(targetMember.getId()) + .gameName(targetMember.getGameName()) + .memberProfileImg(targetMember.getProfileImage()) + .friend(friendService.isFriend(member, targetMember)) + .blocked(false) + .system(systemFlagDTO) + .chatMessageList(null) .build(); - - return ChatroomEnterDTO.builder() - .uuid(savedChatroom.getUuid()) - .memberId(targetMember.getId()) - .gameName(targetMember.getGameName()) - .memberProfileImg(targetMember.getProfileImage()) - .friend(friendService.isFriend(member, targetMember)) - .blocked(false) - .system(systemFlagDTO) - .chatMessageList(null) - .build(); - } - + }); } /** @@ -308,97 +161,26 @@ public ChatResponse.ChatroomEnterDTO startChatroomByBoardId(Long memberId, Long */ public String startChatroomByMatching(Long memberId1, Long memberId2) { + // 매칭 대상 회원이 동일한 회원인 경우 + MemberUtils.validateDifferentMembers(memberId1, memberId2, + ErrorStatus.CHAT_TARGET_MEMBER_ID_INVALID); + Member member1 = profileService.findMember(memberId1); Member member2 = profileService.findMember(memberId2); - // 채팅 대상 회원과의 chatroom 존재 여부 조회 - Optional chatroom = chatroomRepository.findChatroomByMemberIds( - member1.getId(), member2.getId()); - - Chatroom finalChatroom = null; - - // 채팅방이 기존에 존재하는 경우 - if (chatroom.isPresent()) { - - // 각 회원의 lastJoinDate가 null 인 경우, 현재 시각으로 업데이트 - MemberChatroom memberChatroom1 = memberChatroomRepository.findByMemberIdAndChatroomId( - member1.getId(), chatroom.get().getId()) - .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); - - LocalDateTime lastJoinDate = LocalDateTime.now(); - - if (memberChatroom1.getLastJoinDate() == null) { - memberChatroom1.updateLastJoinDate(lastJoinDate); - } - - MemberChatroom memberChatroom2 = memberChatroomRepository.findByMemberIdAndChatroomId( - member2.getId(), chatroom.get().getId()) - .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); - - if (memberChatroom2.getLastJoinDate() == null) { - memberChatroom2.updateLastJoinDate(lastJoinDate); - } - - finalChatroom = chatroom.get(); - - } else { // 채팅방이 기존에 존재하지 않는 경우 - // chatroom 엔티티 생성 - String uuid = UUID.randomUUID().toString(); - Chatroom newChatroom = Chatroom.builder() - .uuid(uuid) - .startMember(null) - .build(); - - Chatroom savedChatroom = chatroomRepository.save(newChatroom); - - finalChatroom = savedChatroom; - - LocalDateTime now = LocalDateTime.now(); + Chatroom chatroom = chatroomRepository + .findChatroomByMemberIds(member1.getId(), member2.getId()) + .map(existingChatroom -> updateLastJoinDateWithOutSocket(member1, member2, + existingChatroom, + LocalDateTime.now())) // 기존 채팅방 존재하는 경우, 서로의 lastJoinDate가 null이면 현재 시각으로 업데이트 + .orElseGet(() -> createNewChatroom(member1, member2, + LocalDateTime.now())); // 기존 채팅방 존재하지 않는 경우, 새로운 채팅방 생성 - // MemberChatroom 엔티티 생성 및 연관관계 매핑 - // member1의 MemberChatroom 엔티티 - MemberChatroom memberChatroom1 = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(now) - .chatroom(newChatroom) - .build(); - memberChatroom1.setMember(member1); - memberChatroomRepository.save(memberChatroom1); + // 두 회원에게 매칭 시스템 메시지 생성 및 저장 + createAndSaveSystemChat(chatroom, member1, MATCHING_SYSTEM_MESSAGE, null); + createAndSaveSystemChat(chatroom, member2, MATCHING_SYSTEM_MESSAGE, null); - // member2의 MemberChatroom 엔티티 - MemberChatroom memberChatroom2 = MemberChatroom.builder() - .lastViewDate(null) - .lastJoinDate(now) - .chatroom(newChatroom) - .build(); - memberChatroom2.setMember(member2); - memberChatroomRepository.save(memberChatroom2); - } - - // 매칭 시스템 메시지 생성 및 저장 - // 시스템 메시지 전송자 member 엔티티 조회 - Member systemMember = profileService.findMember(0L); - - Chat matchingSystemChat1 = Chat.builder() - .contents(MATCHING_SYSTEM_MESSAGE) - .chatroom(finalChatroom) - .fromMember(systemMember) - .toMember(member1) - .sourceBoard(null) - .build(); - - Chat matchingSystemChat2 = Chat.builder() - .contents(MATCHING_SYSTEM_MESSAGE) - .chatroom(finalChatroom) - .fromMember(systemMember) - .toMember(member2) - .sourceBoard(null) - .build(); - - chatRepository.save(matchingSystemChat1); - chatRepository.save(matchingSystemChat2); - - return finalChatroom.getUuid(); + return chatroom.getUuid(); } /** @@ -425,36 +207,10 @@ public ChatResponse.ChatroomEnterDTO enterChatroom(String chatroomUuid, Long mem chatroom.getId(), memberId); // 내가 채팅 상대 회원을 차단한 경우 - if (MemberUtils.isBlocked(member, targetMember)) { - throw new ChatHandler(ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); - } - - // 상대방이 나를 차단 && 내가 이 채팅방을 나간 상태인 경우 - if (MemberUtils.isBlocked(targetMember, member) - && memberChatroom.getLastJoinDate() == null) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); - } - - // 최근 메시지 내역 조회 - Slice recentChats = chatRepository.findRecentChats(chatroom.getId(), - memberChatroom.getId(), member.getId()); + MemberUtils.validateBlocked(member, targetMember, + ErrorStatus.CHAT_TARGET_IS_BLOCKED_CHAT_START_FAILED); - // 해당 채팅방의 lastViewDate 업데이트 - memberChatroom.updateLastViewDate(LocalDateTime.now()); - - // ChatMessageListDTO 생성 - ChatResponse.ChatMessageListDTO chatMessageListDTO = ChatConverter.toChatMessageListDTO( - recentChats); - - return ChatroomEnterDTO.builder() - .uuid(chatroomUuid) - .memberId(targetMember.getId()) - .gameName(targetMember.getGameName()) - .memberProfileImg(targetMember.getProfileImage()) - .friend(friendService.isFriend(member, targetMember)) - .blocked(MemberUtils.isBlocked(targetMember, member)) - .chatMessageList(chatMessageListDTO) - .build(); + return enterExistingChatroom(member, targetMember, chatroom, null); } /** @@ -481,49 +237,31 @@ public Chat addChat(ChatRequest.ChatCreateRequest request, String chatroomUuid, // 대화 상대 회원 조회 Member targetMember = memberChatroomRepository.findTargetMemberByChatroomIdAndMemberId( chatroom.getId(), memberId); - if (MemberUtils.isBlocked(member, targetMember)) { - throw new ChatHandler(ErrorStatus.CHAT_TARGET_IS_BLOCKED_SEND_CHAT_FAILED); - } - if (MemberUtils.isBlocked(targetMember, member)) { - throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_SEND_CHAT_FAILED); - } + MemberUtils.validateBlocked(member, targetMember, + ErrorStatus.CHAT_TARGET_IS_BLOCKED_SEND_CHAT_FAILED); + MemberUtils.validateBlocked(targetMember, member, + ErrorStatus.BLOCKED_BY_CHAT_TARGET_SEND_CHAT_FAILED); // 등록해야 할 시스템 메시지가 있는 경우 if (request.getSystem() != null) { SystemFlagRequest systemFlag = request.getSystem(); Optional board = boardRepository.findById(systemFlag.getBoardId()); - // 시스템 메시지 전송자 member 엔티티 조회 - Member systemMember = profileService.findMember(0L); - // member 대상 시스템 메시지 생성 String messageContent = systemFlag.getFlag().equals(1) ? POST_SYSTEM_MESSAGE_TO_MEMBER_INIT : POST_SYSTEM_MESSAGE_TO_MEMBER; - Chat systemChatToMember = Chat.builder() - .contents(messageContent) - .chatroom(chatroom) - .fromMember(systemMember) - .toMember(member) - .sourceBoard(board.orElse(null)) - .build(); + Chat systemChatToMember = createAndSaveSystemChat(chatroom, member, messageContent, + board.orElse(null)); // targetMember 대상 시스템 메시지 생성 - Chat systemChatToTargetMember = Chat.builder() - .contents(POST_SYSTEM_MESSAGE_TO_TARGET_MEMBER) - .chatroom(chatroom) - .fromMember(systemMember) - .toMember(targetMember) - .sourceBoard(board.orElse(null)) - .build(); - - Chat memberSystemChat = chatRepository.save(systemChatToMember); - Chat targetMemberSystemChat = chatRepository.save(systemChatToTargetMember); - chatRepository.flush(); + Chat systemChatToTargetMember = createAndSaveSystemChat(chatroom, targetMember, + POST_SYSTEM_MESSAGE_TO_TARGET_MEMBER, + board.orElse(null)); - updateLastJoinDateBySystemChat(memberChatroom, memberSystemChat.getCreatedAt(), - targetMemberSystemChat.getCreatedAt()); + updateLastJoinDateBySystemChat(memberChatroom, systemChatToMember.getCreatedAt(), + systemChatToTargetMember.getCreatedAt()); } @@ -539,7 +277,7 @@ public Chat addChat(ChatRequest.ChatCreateRequest request, String chatroomUuid, if (request.getSystem() == null) { updateLastViewDateByAddChat(memberChatroom, savedChat.getCreatedAt()); } else { - updateOnlyLastViewDate(memberChatroom, savedChat.getCreatedAt()); + memberChatroom.updateLastViewDate(savedChat.getCreatedAt()); } return savedChat; @@ -596,6 +334,45 @@ public void exitChatroom(String chatroomUuid, Long memberId) { } + + /* private 메소드 */ + + /** + * 두 회원의 lastJoinDate가 각각 null인 경우, 해당 lastJoinDate로 업데이트. socket API 호출 미포함 + * + * @param member1 + * @param member2 + * @param chatroom + * @param lastJoinDate + * @return + */ + private Chatroom updateLastJoinDateWithOutSocket(Member member1, Member member2, + Chatroom chatroom, LocalDateTime lastJoinDate) { + + updateLastJoinDateIfNull(member1, chatroom, lastJoinDate); + updateLastJoinDateIfNull(member2, chatroom, lastJoinDate); + return chatroom; + } + + /** + * member의 lastJoinDate가 null이 아닌 경우, lastJoinDate 업데이트 + * + * @param member + * @param chatroom + * @param lastJoinDate + */ + private void updateLastJoinDateIfNull(Member member, Chatroom chatroom, + LocalDateTime lastJoinDate) { + MemberChatroom memberChatroom = memberChatroomRepository + .findByMemberIdAndChatroomId(member.getId(), chatroom.getId()) + .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); + + if (memberChatroom.getLastJoinDate() == null) { + memberChatroom.updateLastJoinDate(lastJoinDate); + } + } + + /** * 채팅 등록 시 나와 상대방의 lastViewDate 업데이트 * @@ -676,14 +453,130 @@ private void updateLastJoinDateBySystemChat(MemberChatroom memberChatroom, } /** - * 해당 memberChatroom의 lastViewDate만 업데이트 + * 두 회원 간 새로운 채팅방 생성 * - * @param memberChatroom - * @param lastViewDate + * @param member1 + * @param member2 + * @return */ - private void updateOnlyLastViewDate(MemberChatroom memberChatroom, - LocalDateTime lastViewDate) { - memberChatroom.updateLastViewDate(lastViewDate); + private Chatroom createNewChatroom(Member member1, Member member2, LocalDateTime lastJoinDate) { + String uuid = UUID.randomUUID().toString(); + Chatroom newChatroom = Chatroom.builder() + .uuid(uuid) + .startMember(null) + .build(); + + chatroomRepository.save(newChatroom); + + createAndSaveMemberChatroom(member1, newChatroom, lastJoinDate); + createAndSaveMemberChatroom(member2, newChatroom, lastJoinDate); + + return newChatroom; } + + /** + * 해댕 회원 및 채팅방에 대한 MemberChatroom 엔티티 생성 및 저장 + * + * @param member + * @param chatroom + * @param lastJoinDate + */ + private void createAndSaveMemberChatroom(Member member, Chatroom chatroom, + LocalDateTime lastJoinDate) { + MemberChatroom memberChatroom = MemberChatroom.builder() + .lastViewDate(null) + .lastJoinDate(lastJoinDate) + .chatroom(chatroom) + .build(); + memberChatroom.setMember(member); + memberChatroomRepository.save(memberChatroom); + } + + /** + * 시스템 메시지 생성 및 저장 + * + * @param chatroom + * @param toMember + * @param content + * @param sourceBoard + * @return + */ + private Chat createAndSaveSystemChat(Chatroom chatroom, Member toMember, + String content, Board sourceBoard) { + Member systemMember = profileService.findMember(0L); + + Chat systemChat = Chat.builder() + .contents(content) + .chatroom(chatroom) + .fromMember(systemMember) + .toMember(toMember) + .sourceBoard(sourceBoard) + .build(); + + return chatRepository.save(systemChat); + } + + /** + * member를 해당 chatroom에 입장 처리 + * + * @param member + * @param targetMember + * @param chatroom + * @return + */ + private ChatResponse.ChatroomEnterDTO enterExistingChatroom(Member member, Member targetMember, + Chatroom chatroom, Long boardId) { + MemberChatroom memberChatroom = memberChatroomRepository.findByMemberIdAndChatroomId( + member.getId(), chatroom.getId()) + .orElseThrow(() -> new ChatHandler(ErrorStatus.CHATROOM_ACCESS_DENIED)); + + // 상대방이 나를 차단 && 내가 퇴장한 상태 + if (MemberUtils.isBlocked(targetMember, member) + && memberChatroom.getLastJoinDate() == null) { + throw new ChatHandler(ErrorStatus.BLOCKED_BY_CHAT_TARGET_CHAT_START_FAILED); + } + + // 최근 메시지 내역 조회 + Slice recentChats = chatRepository.findRecentChats(chatroom.getId(), + memberChatroom.getId(), member.getId()); + + // lastViewDate 업데이트 + memberChatroom.updateLastViewDate(LocalDateTime.now()); + + // ChatMessageListDTO 생성 + ChatResponse.ChatMessageListDTO chatMessageListDTO = ChatConverter.toChatMessageListDTO( + recentChats); + + // 시스템 플래그 생성, boardId가 null인 경우 systemFlagDTO도 null + ChatResponse.SystemFlagDTO systemFlagDTO = createSystemFlagDTO(memberChatroom, boardId); + + return ChatroomEnterDTO.builder() + .uuid(chatroom.getUuid()) + .memberId(targetMember.getId()) + .gameName(targetMember.getGameName()) + .memberProfileImg(targetMember.getProfileImage()) + .friend(friendService.isFriend(member, targetMember)) + .blocked(MemberUtils.isBlocked(targetMember, member)) + .system(systemFlagDTO) + .chatMessageList(chatMessageListDTO) + .build(); + } + + /** + * boardId 값에 따른 systemFlag dto 생성 + * + * @param memberChatroom + * @param boardId + * @return + */ + private ChatResponse.SystemFlagDTO createSystemFlagDTO(MemberChatroom memberChatroom, + Long boardId) { + if (boardId == null) { + return null; + } + return memberChatroom.getLastJoinDate() == null + ? ChatResponse.SystemFlagDTO.builder().flag(1).boardId(boardId).build() + : ChatResponse.SystemFlagDTO.builder().flag(2).boardId(boardId).build(); + } } diff --git a/src/main/java/com/gamegoo/service/chat/ChatQueryService.java b/src/main/java/com/gamegoo/service/chat/ChatQueryService.java index 1422cad8..3ae9ebd9 100644 --- a/src/main/java/com/gamegoo/service/chat/ChatQueryService.java +++ b/src/main/java/com/gamegoo/service/chat/ChatQueryService.java @@ -13,6 +13,7 @@ import com.gamegoo.service.member.ProfileService; import com.gamegoo.util.DatetimeUtil; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -123,6 +124,31 @@ public Slice getChatMessagesByCursor(String chatroomUuid, Long memberId, L } } + /** + * 해당 회원의 안읽은 메시지가 속한 채팅방의 uuid list를 리턴 + * + * @param memberId + * @return + */ + public List getUnreadChatroomUuids(Long memberId) { + Member member = profileService.findMember(memberId); + + List activeMemberChatroom = memberChatroomRepository.findActiveMemberChatroomOrderByLastChat( + member.getId()); + + List unreadChatroomUuids = activeMemberChatroom.stream().map(memberChatroom -> { + Integer unreadCnt = chatRepository.countUnreadChats( + memberChatroom.getChatroom().getId(), memberChatroom.getId(), member.getId()); + + return unreadCnt > 0 ? memberChatroom.getChatroom().getUuid() : null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + return unreadChatroomUuids; + } + + /** * 두 회원 간의 Chatroom 엔티티 반환 * diff --git a/src/main/java/com/gamegoo/util/MemberUtils.java b/src/main/java/com/gamegoo/util/MemberUtils.java index 86d5eed7..9bb202ea 100644 --- a/src/main/java/com/gamegoo/util/MemberUtils.java +++ b/src/main/java/com/gamegoo/util/MemberUtils.java @@ -2,6 +2,7 @@ import com.gamegoo.apiPayload.code.status.ErrorStatus; import com.gamegoo.apiPayload.exception.handler.MemberHandler; +import com.gamegoo.domain.Block; import com.gamegoo.domain.member.Member; public class MemberUtils { @@ -16,7 +17,23 @@ public class MemberUtils { public static boolean isBlocked(Member member, Member targetMember) { return member.getBlockList().stream() - .anyMatch(block -> block.getBlockedMember().equals(targetMember)); + .anyMatch(block -> block.getBlockedMember().equals(targetMember)); + } + + /** + * member가 targetMember를 차단한 상태이면, 해당 ErrorStatus로 에러 처리 + * + * @param member + * @param targetMember + * @param errorStatus + */ + public static void validateBlocked(Member member, Member targetMember, + ErrorStatus errorStatus) { + for (Block block : member.getBlockList()) { + if (block.getBlockedMember().equals(targetMember)) { + throw new MemberHandler(errorStatus); + } + } } /** @@ -31,5 +48,19 @@ public static boolean checkBlind(Member member) { return false; } + /** + * 두 회원이 서로 다른 회원인지 검증 및 검증 실패 시 에러 처리 + * + * @param memberId1 + * @param memberId2 + * @param errorStatus + */ + public static void validateDifferentMembers(Long memberId1, Long memberId2, + ErrorStatus errorStatus) { + if (memberId1.equals(memberId2)) { + throw new MemberHandler(errorStatus); + } + } + }