diff --git a/src/main/java/org/prgms/locomocoserver/chat/application/ChatActivityService.java b/src/main/java/org/prgms/locomocoserver/chat/application/ChatActivityService.java index 66935718..de7758a1 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/application/ChatActivityService.java +++ b/src/main/java/org/prgms/locomocoserver/chat/application/ChatActivityService.java @@ -1,9 +1,9 @@ package org.prgms.locomocoserver.chat.application; import lombok.RequiredArgsConstructor; -import org.prgms.locomocoserver.chat.domain.ChatParticipant; -import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoCustomRepository; -import org.prgms.locomocoserver.chat.domain.querydsl.ChatParticipantCustomRepository; +import org.bson.types.ObjectId; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivity; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivityRepository; import org.prgms.locomocoserver.chat.dto.request.ChatActivityRequestDto; import org.prgms.locomocoserver.chat.exception.ChatErrorType; import org.prgms.locomocoserver.chat.exception.ChatException; @@ -14,21 +14,13 @@ @RequiredArgsConstructor public class ChatActivityService { - private final ChatParticipantCustomRepository chatParticipantRepository; - private final ChatMessageMongoCustomRepository chatMessageMongoCustomRepository; + private final ChatActivityRepository chatActivityRepository; @Transactional public void updateLastReadMessage(Long chatRoomId, ChatActivityRequestDto requestDto) { - ChatParticipant chatParticipant = chatParticipantRepository.findByUserIdAndChatRoomId(requestDto.userId(), chatRoomId) + ChatActivity chatActivity = chatActivityRepository.findByUserIdAndChatRoomId(requestDto.userId().toString(), String.valueOf(chatRoomId)) .orElseThrow(() -> new ChatException(ChatErrorType.CHAT_PARTICIPANT_NOT_FOUND)); - chatParticipant.updateLastReadMessageId(requestDto.lastReadMessageId()); + chatActivity.updateLastReadMessage(requestDto.userId().toString(), new ObjectId(requestDto.lastReadMessageId())); } - @Transactional(readOnly = true) - public int unReadMessageCount(Long roomId, String lastReadMsgId) { - if (lastReadMsgId == null) { - return 0; - } - return chatMessageMongoCustomRepository.unReadMessageCount(roomId, lastReadMsgId); - } } diff --git a/src/main/java/org/prgms/locomocoserver/chat/application/ChatRoomService.java b/src/main/java/org/prgms/locomocoserver/chat/application/ChatRoomService.java index 30d0b5a1..1b125f2c 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/application/ChatRoomService.java +++ b/src/main/java/org/prgms/locomocoserver/chat/application/ChatRoomService.java @@ -2,36 +2,46 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.prgms.locomocoserver.chat.dao.ChatActivityDao; import org.prgms.locomocoserver.chat.domain.ChatParticipant; -import org.prgms.locomocoserver.chat.domain.ChatParticipantRepository; import org.prgms.locomocoserver.chat.domain.ChatRoom; import org.prgms.locomocoserver.chat.domain.ChatRoomRepository; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivity; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivityRepository; +import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoCustomRepository; import org.prgms.locomocoserver.chat.domain.querydsl.ChatParticipantCustomRepository; import org.prgms.locomocoserver.chat.domain.querydsl.ChatRoomCustomRepository; +import org.prgms.locomocoserver.chat.dto.ChatMessageBriefDto; import org.prgms.locomocoserver.chat.dto.ChatMessageDto; import org.prgms.locomocoserver.chat.dto.ChatRoomDto; +import org.prgms.locomocoserver.chat.dto.ChatUserInfo; import org.prgms.locomocoserver.chat.dto.request.ChatCreateRequestDto; import org.prgms.locomocoserver.chat.dto.request.ChatEnterRequestDto; import org.prgms.locomocoserver.chat.dto.request.ChatMessageRequestDto; import org.prgms.locomocoserver.chat.exception.ChatErrorType; import org.prgms.locomocoserver.chat.exception.ChatException; import org.prgms.locomocoserver.user.domain.User; +import org.prgms.locomocoserver.user.domain.querydsl.UserCustomRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class ChatRoomService { - private final MongoChatMessageService mongoChatMessageService; private final ChatRoomRepository chatRoomRepository; private final ChatRoomCustomRepository chatRoomCustomRepository; private final ChatParticipantCustomRepository chatParticipantCustomRepository; - private final ChatActivityService chatActivityService; + private final UserCustomRepository userCustomRepository; + private final ChatMessageMongoCustomRepository chatMessageMongoCustomRepository; + private final ChatActivityRepository chatActivityRepository; private final StompChatService stompChatService; private final ChatMessagePolicy chatMessagePolicy; @@ -46,6 +56,9 @@ public void enterChatRoom(ChatEnterRequestDto requestDto) { ChatParticipant chatParticipant = chatParticipantCustomRepository.save(ChatParticipant.builder().user(requestDto.participant()) .chatRoom(chatRoom).build()).orElseThrow(() -> new RuntimeException("채팅방 참여에 실패했습니다.")); + chatActivityRepository.save(ChatActivity.builder().chatRoomId(requestDto.chatRoomId().toString()) + .userId(requestDto.participant().getId().toString()).build()); + chatRoom.addChatParticipant(chatParticipant); stompChatService.sendToSubscribers(chatMessageDto); } @@ -59,7 +72,8 @@ public ChatRoom createChatRoom(ChatCreateRequestDto requestDto) { chatRoom.addChatParticipant(chatParticipant); chatRoomRepository.save(chatRoom); // mysql chat room create - mongoChatMessageService.createChatRoom(chatRoom.getId()); // mongo chat room create + chatActivityRepository.save(ChatActivity.builder() + .chatRoomId(chatRoom.getId().toString()).userId(requestDto.creator().getId().toString()).build()); chatMessagePolicy.saveEnterMessage(chatRoom.getId(), chatParticipant.getUser()); @@ -68,7 +82,9 @@ public ChatRoom createChatRoom(ChatCreateRequestDto requestDto) { @Transactional public ChatMessageDto saveChatMessage(ChatMessageRequestDto requestDto) { - return chatMessagePolicy.saveChatMessage(requestDto.chatRoomId(), requestDto); + ChatMessageDto chatMessageDto = chatMessagePolicy.saveChatMessage(requestDto.chatRoomId(), requestDto); + + return chatMessageDto; } @Transactional @@ -82,21 +98,42 @@ public ChatMessageDto saveEnterMessage(ChatEnterRequestDto requestDto) { } @Transactional(readOnly = true) - public List getAllChatRoom(Long userId, Long cursor, int pageSize) { - if (cursor == null) cursor = Long.MAX_VALUE; - List chatRooms = chatRoomCustomRepository.findByParticipantsId(userId, cursor, pageSize); + public List getAllChatRoom(Long userId, String cursor, int pageSize) { + if (cursor == null) cursor = LocalDateTime.now().toString(); - List chatRoomDtos = chatRooms.stream() + log.info("START findByParticipantsId"); + List chatRooms = chatRoomCustomRepository.findByParticipantsId(userId, cursor, pageSize); + log.info("END findByParticipantsId"); + + List chatRoomIds = createChatRoomIds(chatRooms); + log.info("START findLastMessagesAndUnReadMsgCount"); + List lastMessages = chatMessageMongoCustomRepository.findLastMessagesAndUnReadMsgCount(userId.toString(), chatRoomIds); + log.info("END findLastMessagesAndUnreadMsgCount"); + + Map lastMsgMongoMap = lastMessages.stream() + .collect(Collectors.toMap( + dao -> Long.parseLong(dao.chatRoomId()), + dao -> dao + )); + + List userIds = createUserIds(lastMessages); + log.info("START findByIdIn"); + Map userMap = userCustomRepository.findAllWithImageByIdIn(userIds).stream() + .collect(Collectors.toMap(User::getId, user -> user)); + log.info("END findByIdIn"); + + log.info("START dto Change"); + return chatRooms.stream() .map(chatRoom -> { - ChatMessageDto lastMessageDto = chatMessagePolicy.getLastChatMessage(chatRoom.getId()); - ChatParticipant chatParticipant = getChatParticipant(chatRoom, userId); + ChatActivityDao dao = lastMsgMongoMap.get(chatRoom.getId()); + ChatMessageBriefDto lastMessageDto = ChatMessageBriefDto.of(dao.chatRoomId(), dao.chatMessageId().toString(), dao.senderId(), dao.message(), dao.createdAt()); + User user = userMap.get(Long.parseLong(dao.senderId())); + ChatUserInfo chatUserInfo = user.getDeletedAt() == null ? ChatUserInfo.of(user) : ChatUserInfo.deletedUser(user.getId()); - int unReadMsgCnt = chatActivityService.unReadMessageCount(chatRoom.getId(), chatParticipant.getLastReadMessageId()); - return ChatRoomDto.of(chatRoom, unReadMsgCnt, lastMessageDto); + return ChatRoomDto.of(chatRoom, Integer.parseInt(dao.unReadMsgCnt()), lastMessageDto, chatUserInfo); }) - .filter(Objects::nonNull) // null인 경우 제외 + .filter(Objects::nonNull) .toList(); - return chatRoomDtos; } @Transactional(readOnly = true) @@ -136,10 +173,15 @@ private boolean isParticipantExist(ChatRoom chatRoom, User user) { .anyMatch(chatParticipant -> chatParticipant.getUser().getId().equals(user.getId())); } - private ChatParticipant getChatParticipant(ChatRoom chatRoom, Long userId) { - return chatRoom.getChatParticipants().stream() - .filter(p -> p.getUser().getId().equals(userId)) - .findFirst() - .orElse(null); + private List createChatRoomIds(List chatRooms) { + return chatRooms.stream() + .map(chatRoom -> chatRoom.getId().toString()).collect(Collectors.toList()); + } + + private List createUserIds(List lastMessages) { + return lastMessages.stream() + .map(dao -> Long.valueOf(dao.senderId())) + .distinct() + .collect(Collectors.toList()); } } diff --git a/src/main/java/org/prgms/locomocoserver/chat/application/MongoChatMessageService.java b/src/main/java/org/prgms/locomocoserver/chat/application/MongoChatMessageService.java index cf2558ed..e186417d 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/application/MongoChatMessageService.java +++ b/src/main/java/org/prgms/locomocoserver/chat/application/MongoChatMessageService.java @@ -5,17 +5,17 @@ import org.prgms.locomocoserver.chat.domain.ChatRoomRepository; import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongo; import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoCustomRepository; +import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoRepository; +import org.prgms.locomocoserver.chat.domain.querydsl.ChatRoomCustomRepository; import org.prgms.locomocoserver.chat.dto.ChatMessageDto; import org.prgms.locomocoserver.chat.dto.ChatUserInfo; import org.prgms.locomocoserver.chat.dto.request.ChatMessageRequestDto; import org.prgms.locomocoserver.chat.exception.ChatErrorType; import org.prgms.locomocoserver.chat.exception.ChatException; -import org.prgms.locomocoserver.chat.domain.querydsl.ChatRoomCustomRepository; import org.prgms.locomocoserver.user.application.UserService; import org.prgms.locomocoserver.user.domain.User; import org.prgms.locomocoserver.user.exception.UserException; import org.springframework.context.annotation.Primary; -import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; @Primary @@ -31,56 +30,40 @@ @RequiredArgsConstructor public class MongoChatMessageService implements ChatMessagePolicy { - private static final String BASE_CHATROOM_NAME = "chat_messages_"; - private final MongoTemplate mongoTemplate; + private final ChatMessageMongoRepository chatMessageMongoRepository; private final ChatMessageMongoCustomRepository chatMessageMongoCustomRepository; private final UserService userService; private final ChatRoomRepository chatRoomRepository; private final ChatRoomCustomRepository chatRoomCustomRepository; - @Transactional - public ChatRoom createChatRoom(Long roomId) { - String collectionName = BASE_CHATROOM_NAME + roomId; - - if (!mongoTemplate.collectionExists(collectionName)) { - mongoTemplate.createCollection(collectionName); - } - - return chatRoomRepository.findById(roomId).orElseThrow(() -> new ChatException(ChatErrorType.CHATROOM_NOT_FOUND)); - } - @Transactional public ChatMessageDto saveEnterMessage(Long roomId, User sender) { - String collectionName = BASE_CHATROOM_NAME + roomId; - ChatMessageMongo chatMessageMongo = mongoTemplate.save(toEnterMessage(sender), collectionName); + ChatMessageMongo chatMessageMongo = chatMessageMongoRepository.save(toEnterMessage(roomId, sender)); return ChatMessageDto.of(roomId, chatMessageMongo, ChatUserInfo.of(sender)); } @Transactional public ChatMessageDto saveChatMessage(Long roomId, ChatMessageRequestDto message) { - String collectionName = BASE_CHATROOM_NAME + roomId; User participant = userService.getById(message.senderId()); ChatRoom chatRoom = chatRoomRepository.findByIdAndDeletedAtIsNull(roomId) .orElseThrow(() -> new ChatException(ChatErrorType.CHATROOM_NOT_FOUND)); - ChatMessageMongo chatMessageMongo = mongoTemplate.save(message.toChatMessageMongo(false, null), collectionName); + ChatMessageMongo chatMessageMongo = chatMessageMongoRepository.save(message.toChatMessageMongo(roomId, false, null)); chatRoom.updateUpdatedAt(); return ChatMessageDto.of(roomId, chatMessageMongo, ChatUserInfo.of(participant)); } - @Override @Transactional public ChatMessageDto saveChatMessageWithImage(Long roomId, List imageUrls, ChatMessageRequestDto request) { - String collectionName = BASE_CHATROOM_NAME + roomId; User participant = userService.getById(request.senderId()); ChatRoom chatRoom = chatRoomRepository.findByIdAndDeletedAtIsNull(roomId) .orElseThrow(() -> new ChatException(ChatErrorType.CHATROOM_NOT_FOUND)); - ChatMessageMongo chatMessageMongo = mongoTemplate.save(request.toChatMessageMongo(false, imageUrls), collectionName); + ChatMessageMongo chatMessageMongo = chatMessageMongoRepository.save(request.toChatMessageMongo(roomId, false, imageUrls)); chatRoom.updateUpdatedAt(); return ChatMessageDto.of(roomId, imageUrls, chatMessageMongo, ChatUserInfo.of(participant)); @@ -88,7 +71,7 @@ public ChatMessageDto saveChatMessageWithImage(Long roomId, List imageUr @Transactional(readOnly = true) public List getAllChatMessages(Long roomId, String cursorValue, int pageSize) { - List chatMessages = chatMessageMongoCustomRepository.findAllChatMessagesByRoomId(roomId, cursorValue, pageSize); + List chatMessages = chatMessageMongoCustomRepository.findAllChatMessages(roomId, cursorValue, pageSize); Map userMap = getUserMap(roomId); return createChatMessageDtos(roomId, chatMessages, userMap); @@ -96,24 +79,20 @@ public List getAllChatMessages(Long roomId, String cursorValue, @Transactional public void deleteChatMessages(ChatRoom chatRoom) { - chatMessageMongoCustomRepository.deleteAllChatMessages(chatRoom.getId()); + chatMessageMongoRepository.deleteByChatRoomId(chatRoom.getId().toString()); } @Transactional(readOnly = true) public ChatMessageDto getLastChatMessage(Long roomId) { - ChatMessageMongo lastMessage = chatMessageMongoCustomRepository.findLatestMessageByRoomId(roomId) + ChatMessageMongo lastMessage = chatMessageMongoRepository.findTopByChatRoomIdOrderByCreatedAtDesc(roomId.toString()) .orElseThrow(() -> new ChatException(ChatErrorType.CHAT_MESSAGE_NOT_FOUND)); ChatUserInfo chatUserInfo = getChatUserInfo(lastMessage.getSenderId()); return ChatMessageDto.of(roomId, lastMessage, chatUserInfo); } - public String getChatRoomName(Long roomId) { - return BASE_CHATROOM_NAME + roomId; - } - - private ChatMessageMongo toEnterMessage(User participant) { - return ChatMessageMongo.builder().senderId(participant.getId().toString()).createdAt(LocalDateTime.now()) + private ChatMessageMongo toEnterMessage(Long roomId, User participant) { + return ChatMessageMongo.builder().chatRoomId(roomId.toString()).senderId(participant.getId().toString()).createdAt(LocalDateTime.now()) .message(participant.getNickname() + "님이 입장하셨습니다.").isNotice(true).build(); } diff --git a/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityDao.java b/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityDao.java new file mode 100644 index 00000000..3590d4e4 --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityDao.java @@ -0,0 +1,15 @@ +package org.prgms.locomocoserver.chat.dao; + +import org.bson.types.ObjectId; + +import java.time.LocalDateTime; + +public record ChatActivityDao( + String unReadMsgCnt, + String chatRoomId, + ObjectId chatMessageId, + String senderId, + String message, + LocalDateTime createdAt +) { +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityRequestDao.java b/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityRequestDao.java new file mode 100644 index 00000000..af4c7b87 --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/dao/ChatActivityRequestDao.java @@ -0,0 +1,9 @@ +package org.prgms.locomocoserver.chat.dao; + +import org.bson.types.ObjectId; + +public record ChatActivityRequestDao( + String chatRoomId, + ObjectId lastReadMsgId +) { +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivity.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivity.java new file mode 100644 index 00000000..c43d749b --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivity.java @@ -0,0 +1,33 @@ +package org.prgms.locomocoserver.chat.domain.mongo; + +import lombok.Builder; +import lombok.Getter; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.mapping.Document; + +@Getter +@Document(collection = "chat_activity") +@CompoundIndex(def = "{'userId': 1, 'chatRoomId': 1}", name = "user_chatRoom_idx", unique = true) +public class ChatActivity { + @Id + private String id; + private String userId; + private String chatRoomId; + private ObjectId lastReadMsgId; + + @Builder + public ChatActivity(String userId, String chatRoomId) { + this.userId = userId; + this.chatRoomId = chatRoomId; + this.lastReadMsgId = null; + } + + public void updateLastReadMessage(String userId, ObjectId lastReadMsgId) { + if (!this.userId.equals(userId)) { + throw new IllegalArgumentException("Invalid User Id : " + userId); + } + this.lastReadMsgId = lastReadMsgId; + } +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivityRepository.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivityRepository.java new file mode 100644 index 00000000..6f82d6db --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatActivityRepository.java @@ -0,0 +1,11 @@ +package org.prgms.locomocoserver.chat.domain.mongo; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ChatActivityRepository extends MongoRepository { + Optional findByUserIdAndChatRoomId(String userId, String chatRoomId); +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongo.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongo.java index b5eceac6..a618ec0c 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongo.java +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongo.java @@ -3,6 +3,7 @@ import jakarta.persistence.Id; import lombok.Builder; import lombok.Getter; +import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; import java.time.LocalDateTime; @@ -10,23 +11,33 @@ import java.util.List; @Getter -@Document +@Document(collection = "chat_messages") public class ChatMessageMongo { @Id private String id; + @Indexed + private String chatRoomId; private String senderId; private String message; private List imageUrls; private boolean isNotice; + @Indexed private LocalDateTime createdAt; @Builder - public ChatMessageMongo(String senderId, String message, List imageUrls, boolean isNotice, LocalDateTime createdAt) { + public ChatMessageMongo(String chatRoomId, String senderId, String message, List imageUrls, boolean isNotice, LocalDateTime createdAt) { + this.chatRoomId = chatRoomId; this.senderId = senderId; this.message = message; this.imageUrls = imageUrls; this.isNotice = isNotice; this.createdAt = createdAt; } + + public void validate() { + if (message == null || message.trim().isEmpty()) { + throw new IllegalArgumentException("Message cannot be null or empty"); + } + } } diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepository.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepository.java index 18e5278b..f0654fa2 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepository.java +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepository.java @@ -1,14 +1,121 @@ package org.prgms.locomocoserver.chat.domain.mongo; +import com.mongodb.client.model.Filters; +import lombok.RequiredArgsConstructor; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.prgms.locomocoserver.chat.dao.ChatActivityDao; +import org.prgms.locomocoserver.chat.dao.ChatActivityRequestDao; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; import java.util.List; -import java.util.Optional; +import java.util.Map; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class ChatMessageMongoCustomRepository { + + private final MongoTemplate mongoTemplate; + + @Transactional(readOnly = true) + public List findAllChatMessages(Long roomId, String cursorValue, int pageSize) { + ObjectId cursor = cursorValue == null ? new ObjectId(new Date(Long.MAX_VALUE)) : new ObjectId(cursorValue); + Query query = new Query(); + query.with(Sort.by(Sort.Direction.DESC, "_id")); + query.addCriteria(Criteria.where("chatRoomId").is(roomId.toString()) + .and("_id").lt(cursor)); + query.limit(pageSize); + + return mongoTemplate.find(query, ChatMessageMongo.class); + } + + @Transactional(readOnly = true) + public List findLastMessagesAndUnReadMsgCount(String userId, List chatRoomIds) { + // 1. 채팅방별 마지막 메시지 조회 + Map lastMsgMap = findLastMessages(chatRoomIds); + + // 2. 채팅방별 lastReadMsgId 조회 + Map lastReadMsgIdMap = fetchLastReadMsgIds(userId, chatRoomIds); + + // 3. 읽지 않은 메시지 수 계산 기준 생성 + List criteriaList = createUnreadMessageCriteria(lastReadMsgIdMap); + + // 4. 읽지 않은 메시지 수 집계 + return countUnreadMessages(criteriaList, lastMsgMap); + } + + // 마지막 메시지 조회 + private Map findLastMessages(List chatRoomIds) { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(Criteria.where("chatRoomId").in(chatRoomIds)), + Aggregation.sort(Sort.by(Sort.Direction.DESC, "createdAt")), + Aggregation.group("chatRoomId") + .first("chatRoomId").as("chatRoomId") + .first("senderId").as("senderId") + .first("message").as("message") + .first("createdAt").as("createdAt") + .first("_id").as("chatMessageId"), + Aggregation.project("chatRoomId", "senderId", "message", "createdAt", "chatMessageId") + ); + + List lastMessages = mongoTemplate.aggregate(aggregation, "chat_messages", ChatActivityDao.class) + .getMappedResults(); + + return lastMessages.stream() + .collect(Collectors.toMap(ChatActivityDao::chatRoomId, chatActivityDao -> chatActivityDao)); + } + + // lastReadMsgId 조회 + private Map fetchLastReadMsgIds(String userId, List chatRoomIds) { + Query query = new Query(Criteria.where("userId").is(userId).and("chatRoomId").in(chatRoomIds)); + query.fields().include("chatRoomId").include("lastReadMsgId"); + + List results = mongoTemplate.find(query, Document.class, "chat_activity"); + + return results.stream() + .filter(doc -> doc.getObjectId("lastReadMsgId") != null) + .collect(Collectors.toMap( + doc -> doc.getString("chatRoomId"), + doc -> doc.getObjectId("lastReadMsgId") + )); + } -public interface ChatMessageMongoCustomRepository { - Optional findLatestMessageByRoomId(Long roomId); + // 읽지 않은 메시지 수 계산 기준 생성 + private List createUnreadMessageCriteria(Map lastReadMsgIdMap) { + return lastReadMsgIdMap.entrySet().stream() + .map(entry -> Criteria.where("chatRoomId").is(entry.getKey()).and("_id").gt(entry.getValue())) + .collect(Collectors.toList()); + } - List findAllChatMessagesByRoomId(Long roomId, String cursorValue, int pageSize); + // 읽지 않은 메시지 수 집계 + private List countUnreadMessages(List criteriaList, Map lastMsgMap) { + Aggregation aggregation = Aggregation.newAggregation( + Aggregation.match(new Criteria().orOperator(criteriaList.toArray(new Criteria[0]))), + Aggregation.group("chatRoomId").count().as("unreadCount"), + Aggregation.project("unreadCount").and("chatRoomId").previousOperation() + ); - int unReadMessageCount(Long roomId, String lastReadMsgId); + AggregationResults results = mongoTemplate.aggregate(aggregation, "chat_messages", Document.class); - void deleteAllChatMessages(Long roomId); + return results.getMappedResults().stream() + .map(result -> { + String chatRoomId = result.getString("chatRoomId"); + Integer unreadCount = result.getInteger("unreadCount"); + ChatActivityDao chatActivityDao = lastMsgMap.get(chatRoomId); + return new ChatActivityDao(String.valueOf(unreadCount), chatRoomId, chatActivityDao.chatMessageId(), + chatActivityDao.senderId(), chatActivityDao.message(), chatActivityDao.createdAt()); + }) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepositoryImpl.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepositoryImpl.java deleted file mode 100644 index 8caad5db..00000000 --- a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoCustomRepositoryImpl.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.prgms.locomocoserver.chat.domain.mongo; - -import lombok.RequiredArgsConstructor; -import org.bson.types.ObjectId; -import org.prgms.locomocoserver.chat.exception.ChatErrorType; -import org.prgms.locomocoserver.chat.exception.ChatException; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.Optional; - -@Repository -@RequiredArgsConstructor -public class ChatMessageMongoCustomRepositoryImpl implements ChatMessageMongoCustomRepository { - - private static final String BASE_CHATROOM_NAME = "chat_messages_"; - private final MongoTemplate mongoTemplate; - - @Override - @Transactional(readOnly = true) - public Optional findLatestMessageByRoomId(Long roomId) { - String collectionName = getChatRoomName(roomId); - Query query = new Query().with(Sort.by(Sort.Direction.DESC, "_id")).limit(1); - - ChatMessageMongo lastMessage = mongoTemplate.findOne(query, ChatMessageMongo.class, collectionName); - return Optional.of(lastMessage); - } - - @Override - @Transactional(readOnly = true) - public List findAllChatMessagesByRoomId(Long roomId, String cursorValue, int pageSize) { - String collectionName = getChatRoomName(roomId); - Query query = new Query(); - - if (cursorValue != null && !cursorValue.trim().isEmpty() && !"null".equals(cursorValue)) { - ObjectId cursorObjectId = new ObjectId(cursorValue); - query.addCriteria(Criteria.where("_id").lt(cursorObjectId)); - } - query.with(Sort.by(Sort.Direction.DESC, "_id")).limit(pageSize); - - try { - return mongoTemplate.find(query, ChatMessageMongo.class, collectionName); - } catch (Exception e) { - throw new ChatException(ChatErrorType.CHAT_MESSAGE_NOT_FOUND, "채팅방에 메시지가 없습니다."); - } - } - - @Override - @Transactional(readOnly = true) - public int unReadMessageCount(Long roomId, String lastReadMsgId) { - ObjectId lastReadMsgObjectId = new ObjectId(lastReadMsgId); - - Query query = new Query() - .addCriteria(Criteria.where("_id").gt(lastReadMsgObjectId)); - - return (int) mongoTemplate.count(query, ChatMessageMongo.class, getChatRoomName(roomId)); - } - - @Override - @Transactional - public void deleteAllChatMessages(Long roomId) { - String collectionName = getChatRoomName(roomId); - try { - mongoTemplate.dropCollection(collectionName); - } catch (Exception e) { - throw new RuntimeException("채팅방 삭제를 실패했습니다"); - } - } - - private String getChatRoomName(Long roomId) { - return BASE_CHATROOM_NAME + roomId; - } -} diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoRepository.java b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoRepository.java new file mode 100644 index 00000000..97080086 --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/mongo/ChatMessageMongoRepository.java @@ -0,0 +1,19 @@ +package org.prgms.locomocoserver.chat.domain.mongo; + +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +public interface ChatMessageMongoRepository extends MongoRepository { + + Optional findTopByChatRoomIdOrderByCreatedAtDesc(String chatRoomId); + + List findAllByChatRoomId(String chatRoomId); + + long countByChatRoomIdAndIdGreaterThan(String chatRoomId, ObjectId lastReadMsgId); + + void deleteByChatRoomId(String chatRoomId); +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepository.java b/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepository.java index 7d495cfc..25ebb862 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepository.java +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepository.java @@ -6,6 +6,6 @@ import java.util.List; public interface ChatRoomCustomRepository { - List findByParticipantsId(Long userId, Long cursorId, int pageSize); + List findByParticipantsId(Long userId, String cursor, int pageSize); List findParticipantsByRoomId(Long roomId); } diff --git a/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepositoryImpl.java b/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepositoryImpl.java index 8a823757..fb76fb9a 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepositoryImpl.java +++ b/src/main/java/org/prgms/locomocoserver/chat/domain/querydsl/ChatRoomCustomRepositoryImpl.java @@ -10,6 +10,7 @@ import org.prgms.locomocoserver.user.domain.User; import org.springframework.stereotype.Repository; +import java.time.LocalDateTime; import java.util.List; @Repository @@ -19,7 +20,7 @@ public class ChatRoomCustomRepositoryImpl implements ChatRoomCustomRepository { private final JPAQueryFactory queryFactory; @Override - public List findByParticipantsId(Long userId, Long cursorId, int pageSize) { + public List findByParticipantsId(Long userId, String cursor, int pageSize) { QChatRoom chatRoom = QChatRoom.chatRoom; QChatParticipant chatParticipant = QChatParticipant.chatParticipant; QUser user = QUser.user; @@ -31,9 +32,10 @@ public List findByParticipantsId(Long userId, Long cursorId, int pageS .join(chatRoom.chatParticipants, chatParticipant) .where( chatParticipant.user.id.eq(userId) - .and(chatRoom.id.lt(cursorId)) + .and(chatRoom.updatedAt.lt(LocalDateTime.parse(cursor))) .and(chatParticipant.deletedAt.isNull()) ) + .orderBy(chatRoom.updatedAt.desc()) .limit(pageSize) .fetch(); diff --git a/src/main/java/org/prgms/locomocoserver/chat/dto/ChatActivityDto.java b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatActivityDto.java new file mode 100644 index 00000000..5924239f --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatActivityDto.java @@ -0,0 +1,13 @@ +package org.prgms.locomocoserver.chat.dto; + +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivity; + +public record ChatActivityDto( + String userId, + String chatRoomId, + String lastReadMsgId +) { + public static ChatActivityDto of(ChatActivity chatActivity) { + return new ChatActivityDto(chatActivity.getUserId(), chatActivity.getChatRoomId(), chatActivity.getLastReadMsgId().toString()); + } +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/dto/ChatMessageBriefDto.java b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatMessageBriefDto.java new file mode 100644 index 00000000..b1044acc --- /dev/null +++ b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatMessageBriefDto.java @@ -0,0 +1,15 @@ +package org.prgms.locomocoserver.chat.dto; + +import java.time.LocalDateTime; + +public record ChatMessageBriefDto( + String chatRoomId, + String chatMessageId, + String senderId, + String message, + LocalDateTime createdAt +) { + public static ChatMessageBriefDto of(String chatRoomId, String chatMessageId, String senderId, String message, LocalDateTime createdAt) { + return new ChatMessageBriefDto(chatRoomId, chatMessageId, senderId, message, createdAt); + } +} diff --git a/src/main/java/org/prgms/locomocoserver/chat/dto/ChatRoomDto.java b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatRoomDto.java index db800187..3c59d9a2 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/dto/ChatRoomDto.java +++ b/src/main/java/org/prgms/locomocoserver/chat/dto/ChatRoomDto.java @@ -12,9 +12,10 @@ public record ChatRoomDto( int unReadMsgCnt, LocalDateTime createdAt, LocalDateTime updatedAt, - ChatMessageDto lastMessage + ChatMessageBriefDto lastMessage, + ChatUserInfo userInfo ) { - public static ChatRoomDto of(ChatRoom chatRoom, int unReadMsgCnt, ChatMessageDto lastMessage) { - return new ChatRoomDto(chatRoom.getId(), chatRoom.getMogakko().getId(), chatRoom.getName(), chatRoom.getChatParticipants().size(), unReadMsgCnt, chatRoom.getCreatedAt(), chatRoom.getUpdatedAt(), lastMessage); + public static ChatRoomDto of(ChatRoom chatRoom, int unReadMsgCnt, ChatMessageBriefDto lastMessage, ChatUserInfo chatUserInfo) { + return new ChatRoomDto(chatRoom.getId(), chatRoom.getMogakko().getId(), chatRoom.getName(), chatRoom.getChatParticipants().size(), unReadMsgCnt, chatRoom.getCreatedAt(), chatRoom.getUpdatedAt(), lastMessage, chatUserInfo); } } diff --git a/src/main/java/org/prgms/locomocoserver/chat/dto/request/ChatMessageRequestDto.java b/src/main/java/org/prgms/locomocoserver/chat/dto/request/ChatMessageRequestDto.java index 0c36ef01..e55007b6 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/dto/request/ChatMessageRequestDto.java +++ b/src/main/java/org/prgms/locomocoserver/chat/dto/request/ChatMessageRequestDto.java @@ -28,8 +28,9 @@ public ChatMessage toChatMessageEntity(User sender, ChatRoom chatRoom, boolean i .isNotice(isNotice).build(); } - public ChatMessageMongo toChatMessageMongo(boolean isNotice, List imageUrls) { + public ChatMessageMongo toChatMessageMongo(Long chatRoomId, boolean isNotice, List imageUrls) { return ChatMessageMongo.builder() + .chatRoomId(chatRoomId.toString()) .senderId(senderId.toString()) .message(message) .imageUrls(imageUrls) diff --git a/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatActivityController.java b/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatActivityController.java index f74fa059..7ff608d3 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatActivityController.java +++ b/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatActivityController.java @@ -2,7 +2,10 @@ import lombok.RequiredArgsConstructor; import org.prgms.locomocoserver.chat.application.ChatActivityService; +import org.prgms.locomocoserver.chat.dto.ChatActivityDto; import org.prgms.locomocoserver.chat.dto.request.ChatActivityRequestDto; +import org.prgms.locomocoserver.global.annotation.GetUser; +import org.prgms.locomocoserver.user.domain.User; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatRoomController.java b/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatRoomController.java index 8fbc39d9..f4285ffe 100644 --- a/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatRoomController.java +++ b/src/main/java/org/prgms/locomocoserver/chat/presentation/ChatRoomController.java @@ -24,7 +24,7 @@ public class ChatRoomController { @Operation(summary = "채팅방 목록 조회", description = "userId 기반 채팅방 조회") @GetMapping("/chats/rooms/{userId}") public ResponseEntity> getAllChatRooms(@PathVariable Long userId, - @RequestParam(name = "cursor", required = false) Long cursor, + @RequestParam(name = "cursor", required = false) String cursor, @RequestParam(name = "pageSize", defaultValue = "10") int pageSize) { List chatRoomDtos = chatRoomService.getAllChatRoom(userId, cursor, pageSize); return ResponseEntity.ok(chatRoomDtos); diff --git a/src/main/java/org/prgms/locomocoserver/user/domain/UserRepository.java b/src/main/java/org/prgms/locomocoserver/user/domain/UserRepository.java index 0a3285d9..76eec69f 100644 --- a/src/main/java/org/prgms/locomocoserver/user/domain/UserRepository.java +++ b/src/main/java/org/prgms/locomocoserver/user/domain/UserRepository.java @@ -14,4 +14,5 @@ public interface UserRepository extends JpaRepository { Optional findByNicknameAndDeletedAtIsNull(String nickname); @Query("SELECT u FROM User u WHERE u.id IN (SELECT p.user.id FROM Participant p WHERE p.mogakko = :mogakko)") List findAllParticipantsByMogakko(Mogakko mogakko); + List findByIdIn(List ids); } diff --git a/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepository.java b/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepository.java index 65997f79..74ad1738 100644 --- a/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepository.java +++ b/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepository.java @@ -7,7 +7,7 @@ import java.util.Optional; public interface UserCustomRepository { - List findAllById(List userIds); + List findAllWithImageByIdIn(List userIds); List findAllParticipantsByMogakko(Mogakko mogakko); Optional findUserAndImageByUserIdAndDeletedAtIsNull(Long userId); } diff --git a/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepositoryImpl.java b/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepositoryImpl.java index 7254c8da..bf10c7e2 100644 --- a/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepositoryImpl.java +++ b/src/main/java/org/prgms/locomocoserver/user/domain/querydsl/UserCustomRepositoryImpl.java @@ -19,13 +19,14 @@ public class UserCustomRepositoryImpl implements UserCustomRepository { private final JPAQueryFactory queryFactory; @Override - public List findAllById(List userIds) { + public List findAllWithImageByIdIn(List userIds) { QUser user = QUser.user; + QImage image = QImage.image; return queryFactory .selectFrom(user) - .where(user.id.in(userIds) - .and(user.deletedAt.isNull())) + .leftJoin(user.profileImage, image).fetchJoin() + .where(user.id.in(userIds)) .fetch(); } diff --git a/src/test/java/org/prgms/locomocoserver/chat/application/ChatRoomServiceTest.java b/src/test/java/org/prgms/locomocoserver/chat/application/ChatRoomServiceTest.java index a06f6fa3..333e8ab7 100644 --- a/src/test/java/org/prgms/locomocoserver/chat/application/ChatRoomServiceTest.java +++ b/src/test/java/org/prgms/locomocoserver/chat/application/ChatRoomServiceTest.java @@ -1,40 +1,31 @@ package org.prgms.locomocoserver.chat.application; -import static org.assertj.core.api.Assertions.assertThat; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; +import org.bson.types.ObjectId; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.prgms.locomocoserver.chat.domain.ChatMessageRepository; -import org.prgms.locomocoserver.chat.domain.ChatParticipantRepository; -import org.prgms.locomocoserver.chat.domain.ChatRoom; -import org.prgms.locomocoserver.chat.domain.ChatRoomRepository; +import org.prgms.locomocoserver.chat.domain.*; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivity; +import org.prgms.locomocoserver.chat.domain.mongo.ChatActivityRepository; +import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoRepository; import org.prgms.locomocoserver.chat.dto.ChatMessageDto; import org.prgms.locomocoserver.chat.dto.ChatRoomDto; -import org.prgms.locomocoserver.chat.dto.request.ChatActivityRequestDto; import org.prgms.locomocoserver.chat.dto.request.ChatEnterRequestDto; import org.prgms.locomocoserver.chat.dto.request.ChatMessageRequestDto; import org.prgms.locomocoserver.chat.exception.ChatErrorType; import org.prgms.locomocoserver.chat.exception.ChatException; import org.prgms.locomocoserver.global.TestFactory; import org.prgms.locomocoserver.image.domain.ImageRepository; -import org.prgms.locomocoserver.mogakkos.application.MogakkoLikeService; -import org.prgms.locomocoserver.mogakkos.application.MogakkoParticipationService; -import org.prgms.locomocoserver.mogakkos.domain.location.MogakkoLocation; -import org.prgms.locomocoserver.mogakkos.domain.location.MogakkoLocationRepository; -import org.prgms.locomocoserver.mogakkos.domain.vo.AddressInfo; -import org.prgms.locomocoserver.mogakkos.dto.LocationInfoDto; import org.prgms.locomocoserver.mogakkos.application.MogakkoService; import org.prgms.locomocoserver.mogakkos.domain.Mogakko; import org.prgms.locomocoserver.mogakkos.domain.MogakkoRepository; +import org.prgms.locomocoserver.mogakkos.domain.location.MogakkoLocation; +import org.prgms.locomocoserver.mogakkos.domain.location.MogakkoLocationRepository; +import org.prgms.locomocoserver.mogakkos.domain.participants.Participant; import org.prgms.locomocoserver.mogakkos.domain.participants.ParticipantRepository; +import org.prgms.locomocoserver.mogakkos.domain.vo.AddressInfo; +import org.prgms.locomocoserver.mogakkos.dto.LocationInfoDto; import org.prgms.locomocoserver.mogakkos.dto.request.MogakkoCreateRequestDto; -import org.prgms.locomocoserver.mogakkos.dto.request.ParticipationRequestDto; import org.prgms.locomocoserver.mogakkos.dto.response.MogakkoCreateResponseDto; import org.prgms.locomocoserver.user.domain.User; import org.prgms.locomocoserver.user.domain.UserRepository; @@ -45,6 +36,14 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; + @SpringBootTest class ChatRoomServiceTest { @@ -61,9 +60,9 @@ class ChatRoomServiceTest { @Autowired private MongoChatMessageService mongoChatMessageService; @Autowired - private MogakkoParticipationService mogakkoParticipationService; + private ChatMessageMongoRepository chatMessageMongoRepository; @Autowired - private ChatActivityService chatActivityService; + private ChatActivityRepository chatActivityRepository; @Autowired private MogakkoRepository mogakkoRepository; @Autowired @@ -87,6 +86,8 @@ void tearDown() { mogakkoRepository.deleteAll(); userRepository.deleteAll(); imageRepository.deleteAll(); + chatActivityRepository.deleteAll(); + chatMessageMongoRepository.deleteAll(); } @Test @@ -95,21 +96,21 @@ void success_enter_chat_room_as_participant() { // given List dummyUsers = new ArrayList<>(); IntStream.rangeClosed(0, 2).forEach(i -> dummyUsers.add( - User.builder().nickname("name" + i).email(i + "email@gmail.com").birth(LocalDate.EPOCH) - .temperature(36.5).provider("kakao").gender(Gender.MALE).build())); + User.builder().nickname("name" + i).email(i + "email@gmail.com").birth(LocalDate.EPOCH) + .temperature(36.5).provider("kakao").gender(Gender.MALE).build())); userRepository.saveAll(dummyUsers); AddressInfo addressInfo = AddressInfo.builder().address("Martin Garrix").city("Carry You") - .build(); + .build(); MogakkoLocation mogakkoLocation = MogakkoLocation.builder().addressInfo(addressInfo) - .latitude(10.233214).longitude(23.312314).build(); + .latitude(10.233214).longitude(23.312314).build(); MogakkoCreateResponseDto responseDto = mogakkoService.save( - new MogakkoCreateRequestDto(dummyUsers.get(0).getId(), "title", - LocationInfoDto.create(mogakkoLocation), - LocalDateTime.now(), LocalDateTime.now().plusHours(2), - LocalDateTime.now().plusHours(1), - 10, "", List.of())); + new MogakkoCreateRequestDto(dummyUsers.get(0).getId(), "title", + LocationInfoDto.create(mogakkoLocation), + LocalDateTime.now(), LocalDateTime.now().plusHours(2), + LocalDateTime.now().plusHours(1), + 10, "", List.of())); Mogakko mogakko = mogakkoService.getByIdNotDeleted(responseDto.id()); // when @@ -120,7 +121,7 @@ void success_enter_chat_room_as_participant() { TransactionStatus status = tx.getTransaction(new DefaultTransactionDefinition()); ChatRoom chatRoom = chatRoomRepository.findByIdAndDeletedAtIsNull( - mogakko.getChatRoom().getId()).orElseThrow(() -> new ChatException(ChatErrorType.CHATROOM_NOT_FOUND)); + mogakko.getChatRoom().getId()).orElseThrow(() -> new ChatException(ChatErrorType.CHATROOM_NOT_FOUND)); assertThat(chatRoom.getMogakko().getId()).isEqualTo(mogakko.getId()); assertThat(chatRoom.getCreator().getId()).isEqualTo(dummyUsers.get(0).getId()); @@ -137,21 +138,56 @@ void success_get_all_chat_rooms_partial_unread_messages() { User user1 = TestFactory.createUser(); imageRepository.saveAll(List.of(user.getProfileImage(), user1.getProfileImage())); userRepository.saveAll(List.of(user, user1)); - Mogakko mogakko = TestFactory.createMogakko(user); - mogakko = mogakkoRepository.save(mogakko); - ChatRoom chatRoom1 = TestFactory.createChatRoom(user, mogakko); - chatRoomRepository.save(chatRoom1); - mogakkoParticipationService.participate(mogakko.getId(), new ParticipationRequestDto(user1.getId(), null, null)); - mongoChatMessageService.createChatRoom(chatRoom1.getId()); + Mogakko mogakko1 = TestFactory.createMogakko(user); + Mogakko mogakko2 = TestFactory.createMogakko(user1); + mogakkoRepository.saveAll(List.of(mogakko1, mogakko2)); + + ChatRoom chatRoom1 = TestFactory.createChatRoom(user, mogakko1); + ChatRoom chatRoom2 = TestFactory.createChatRoom(user, mogakko2); + chatRoom1 = chatRoomRepository.save(chatRoom1); + chatRoom2 = chatRoomRepository.save(chatRoom2); + + participantRepository.save(new Participant(null, null, user, mogakko1)); + participantRepository.save(new Participant(null, null, user, mogakko2)); + + ChatParticipant chatParticipant1 = chatParticipantRepository.save(new ChatParticipant(user, chatRoom1)); + ChatParticipant chatParticipant2 = chatParticipantRepository.save(new ChatParticipant(user, chatRoom2)); + chatRoom1.addChatParticipant(chatParticipant1); + chatRoom2.addChatParticipant(chatParticipant2); + + ChatActivity chatActivity1 = chatActivityRepository.save(new ChatActivity(user.getId().toString(), chatRoom1.getId().toString())); + ChatActivity chatActivity2 = chatActivityRepository.save(new ChatActivity(user.getId().toString(), chatRoom2.getId().toString())); + ChatMessageDto messageDto1 = mongoChatMessageService.saveChatMessage(chatRoom1.getId(), new ChatMessageRequestDto(chatRoom1.getId(), user.getId(), "test1", null)); ChatMessageDto messageDto2 = mongoChatMessageService.saveChatMessage(chatRoom1.getId(), new ChatMessageRequestDto(chatRoom1.getId(), user.getId(), "test2", null)); + ChatMessageDto messageDto3 = mongoChatMessageService.saveChatMessage(chatRoom2.getId(), new ChatMessageRequestDto(chatRoom2.getId(), user.getId(), "test11", null)); + ChatMessageDto messageDto4 = mongoChatMessageService.saveChatMessage(chatRoom2.getId(), new ChatMessageRequestDto(chatRoom2.getId(), user.getId(), "test22", null)); + ChatMessageDto messageDto5 = mongoChatMessageService.saveChatMessage(chatRoom2.getId(), new ChatMessageRequestDto(chatRoom2.getId(), user.getId(), "test22", null)); + // when - chatActivityService.updateLastReadMessage(chatRoom1.getId(), new ChatActivityRequestDto(user1.getId(), messageDto1.chatMessageId())); - List chatRoomDtos = chatRoomService.getAllChatRoom(user1.getId(), Long.MAX_VALUE, 10); + chatParticipant1.updateLastReadMessageId(messageDto1.chatMessageId()); + chatActivity1.updateLastReadMessage(user.getId().toString(), new ObjectId(messageDto1.chatMessageId())); + chatParticipantRepository.save(chatParticipant1); + chatActivityRepository.save(chatActivity1); + + chatParticipant2.updateLastReadMessageId(messageDto3.chatMessageId()); + chatActivity2.updateLastReadMessage(user.getId().toString(), new ObjectId(messageDto3.chatMessageId())); + chatParticipantRepository.save(chatParticipant2); + chatActivityRepository.save(chatActivity2); + + List chatRoomDtos = chatRoomService.getAllChatRoom(user.getId(), LocalDateTime.now().toString(), 10); // then - assertThat(chatRoomDtos.get(0).unReadMsgCnt()).isEqualTo(1); + assertThat(chatRoomDtos.size()).isEqualTo(2); + + for (ChatRoomDto chatRoomDto : chatRoomDtos) { + if (chatRoomDto.roomId().equals(chatRoom1.getId())) { + assertThat(chatRoomDto.unReadMsgCnt()).isEqualTo(1); + } else if (chatRoomDto.roomId().equals(chatRoom2.getId())) { + assertThat(chatRoomDto.unReadMsgCnt()).isEqualTo(2); + } + } } } diff --git a/src/test/java/org/prgms/locomocoserver/chat/application/MongoChatServiceTest.java b/src/test/java/org/prgms/locomocoserver/chat/application/MongoChatServiceTest.java index 880872eb..52eadfb0 100644 --- a/src/test/java/org/prgms/locomocoserver/chat/application/MongoChatServiceTest.java +++ b/src/test/java/org/prgms/locomocoserver/chat/application/MongoChatServiceTest.java @@ -7,6 +7,7 @@ import org.prgms.locomocoserver.chat.domain.ChatRoom; import org.prgms.locomocoserver.chat.domain.ChatRoomRepository; import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongo; +import org.prgms.locomocoserver.chat.domain.mongo.ChatMessageMongoRepository; import org.prgms.locomocoserver.chat.dto.ChatMessageDto; import org.prgms.locomocoserver.chat.dto.request.ChatMessageRequestDto; import org.prgms.locomocoserver.global.TestFactory; @@ -20,14 +21,17 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.transaction.annotation.Transactional; +import org.testcontainers.shaded.org.apache.commons.lang3.ObjectUtils; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.time.LocalDateTime; import java.util.Base64; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -57,6 +61,8 @@ class MongoChatServiceTest { MogakkoRepository mogakkoRepository; @Autowired ChatParticipantRepository chatParticipantRepository; + @Autowired + ChatMessageMongoRepository chatMessageMongoRepository; private User creator; private ChatRoom chatRoom; @@ -70,6 +76,7 @@ void setUp() { userRepository.deleteAll(); imageRepository.deleteAll(); categoryRepository.deleteAll(); + chatMessageMongoRepository.deleteAll(); User sender = TestFactory.createUser(); imageRepository.save(sender.getProfileImage()); @@ -82,6 +89,17 @@ void setUp() { chatParticipantRepository.save(TestFactory.createChatParticipant(sender, chatRoom)); } + @AfterAll + void tearDown() { + chatParticipantRepository.deleteAll(); + chatRoomRepository.deleteAll(); + mogakkoRepository.deleteAll(); + userRepository.deleteAll(); + imageRepository.deleteAll(); + categoryRepository.deleteAll(); + chatMessageMongoRepository.deleteAll(); + } + @Test @Order(1) @DisplayName("채팅방 입장 메시지를 저장할 수 있다.") @@ -92,10 +110,10 @@ void saveEnterMessage() { // when mongoChatMessageService.saveEnterMessage(roomId, creator); - boolean collectionExists = mongoTemplate.collectionExists("chat_messages_" + roomId); + Optional chatMessage = chatMessageMongoRepository.findTopByChatRoomIdOrderByCreatedAtDesc(roomId.toString()); // then - assertThat(collectionExists).isTrue(); + assertThat(chatMessage).isNotEmpty(); } @Test @@ -106,8 +124,7 @@ void saveChatMessage() { // given Long roomId = chatRoom.getId(); Long senderId = creator.getId(); - String collectionName = mongoChatMessageService.getChatRoomName(roomId); - long beforeMessageCount = mongoTemplate.getCollection(collectionName).countDocuments(); + long beforeMessageCount = chatMessageMongoRepository.findAllByChatRoomId(roomId.toString()).size(); byte[] byteCode = imageToByteArray("src/test/resources/스누피4.jpeg"); String imageBase64 = Base64.getEncoder().encodeToString(byteCode); @@ -117,15 +134,13 @@ void saveChatMessage() { List imageUrls = chatImageService.create(requestDto); mongoChatMessageService.saveChatMessageWithImage(roomId, imageUrls, requestDto); - boolean collectionExists = mongoTemplate.collectionExists(collectionName); - long messageCount = mongoTemplate.getCollection(collectionName).countDocuments(); + long messageCount = chatMessageMongoRepository.findAllByChatRoomId(roomId.toString()).size(); - List messages = mongoTemplate.findAll(ChatMessageMongo.class, collectionName); + List messages = chatMessageMongoRepository.findAllByChatRoomId(roomId.toString()); assertThat(messages).isNotEmpty(); ChatMessageMongo lastMessage = messages.get(messages.size() - 1); // then - assertThat(collectionExists).isTrue(); assertThat(messageCount).isEqualTo(beforeMessageCount + 1); assertThat(lastMessage.getImageUrls().size()).isEqualTo(1); assertThat(lastMessage.getImageUrls().get(0).contains(".jpeg")).isTrue(); @@ -137,10 +152,9 @@ void saveChatMessage() { void getAllChatMessages() { // given Long roomId = chatRoom.getId(); - String collectionName = mongoChatMessageService.getChatRoomName(roomId); // when - List chatMessageMongoList = mongoChatMessageService.getAllChatMessages(roomId, "null", 10); + List chatMessageMongoList = mongoChatMessageService.getAllChatMessages(roomId, null, 10); assertThat(chatMessageMongoList.size()).isEqualTo(2); String cursor = chatMessageMongoList.get(1).chatMessageId(); diff --git a/src/test/java/org/prgms/locomocoserver/chat/repository/ChatRoomRepositoryTest.java b/src/test/java/org/prgms/locomocoserver/chat/repository/ChatRoomRepositoryTest.java index e51dde61..3dfe260d 100644 --- a/src/test/java/org/prgms/locomocoserver/chat/repository/ChatRoomRepositoryTest.java +++ b/src/test/java/org/prgms/locomocoserver/chat/repository/ChatRoomRepositoryTest.java @@ -6,8 +6,9 @@ import org.prgms.locomocoserver.chat.domain.ChatParticipantRepository; import org.prgms.locomocoserver.chat.domain.ChatRoom; import org.prgms.locomocoserver.chat.domain.ChatRoomRepository; -import org.prgms.locomocoserver.chat.dto.ChatRoomDto; import org.prgms.locomocoserver.chat.domain.querydsl.ChatRoomCustomRepository; +import org.prgms.locomocoserver.chat.dto.ChatRoomDto; +import org.prgms.locomocoserver.chat.dto.ChatUserInfo; import org.prgms.locomocoserver.global.TestFactory; import org.prgms.locomocoserver.image.domain.ImageRepository; import org.prgms.locomocoserver.mogakkos.domain.Mogakko; @@ -17,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.time.LocalDateTime; import java.util.List; import static org.junit.Assert.assertThrows; @@ -51,6 +53,7 @@ void setUp() { mogakkoRepository.save(mogakko); ChatRoom chatRoom = TestFactory.createChatRoom(user, mogakko); + chatRoom.updateUpdatedAt(); chatRoom1 = chatRoomRepository.save(chatRoom); ChatParticipant participant = TestFactory.createChatParticipant(user, chatRoom); @@ -83,7 +86,7 @@ void findByParticipantsIdWithJPA() { List chatRooms = chatRoomRepository.findByParticipantsId(user1.getId(), Long.MAX_VALUE, 10); // fetch join X, Transaction X, Lazy Loading assertThrows(LazyInitializationException.class, () -> { - ChatRoomDto.of(chatRooms.get(0), 0, null); + ChatRoomDto.of(chatRooms.get(0), 0, null, ChatUserInfo.of(user1)); }); // then @@ -97,8 +100,8 @@ void findByParticipantsWithQueryDSL() { // given // when - List chatRooms = chatRoomCustomRepository.findByParticipantsId(user1.getId(), Long.MAX_VALUE, 10); - ChatRoomDto.of(chatRooms.get(0), 0,null); + List chatRooms = chatRoomCustomRepository.findByParticipantsId(user1.getId(), LocalDateTime.now().toString(), 10); + ChatRoomDto.of(chatRooms.get(0), 0, null, ChatUserInfo.of(user1)); // then System.out.println("Participants Num : " + chatRooms.size()); diff --git a/src/test/java/org/prgms/locomocoserver/mogakkos/application/MogakkoParticipationTest.java b/src/test/java/org/prgms/locomocoserver/mogakkos/application/MogakkoParticipationTest.java index ef80cbe3..7648d0f1 100644 --- a/src/test/java/org/prgms/locomocoserver/mogakkos/application/MogakkoParticipationTest.java +++ b/src/test/java/org/prgms/locomocoserver/mogakkos/application/MogakkoParticipationTest.java @@ -1,20 +1,6 @@ package org.prgms.locomocoserver.mogakkos.application; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.within; -import static org.prgms.locomocoserver.global.TestFactory.createMogakko; -import static org.prgms.locomocoserver.global.TestFactory.createUser; - import ch.qos.logback.core.util.ExecutorServiceUtil; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.IntStream; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.bson.assertions.Assertions; import org.junit.jupiter.api.AfterEach; @@ -40,6 +26,19 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.*; +import static org.prgms.locomocoserver.global.TestFactory.createMogakko; +import static org.prgms.locomocoserver.global.TestFactory.createUser; + @SpringBootTest class MogakkoParticipationTest { @@ -75,14 +74,14 @@ void setUp() { userRepository.save(testCreator); Long savedMogakkoId = mogakkoService.save( - new MogakkoCreateRequestDto(testCreator.getId(), testMogakko.getTitle(), - new LocationInfoDto("", 127.52d, 27.8621d, null, null), testMogakko.getStartTime(), - testMogakko.getEndTime(), testMogakko.getDeadline(), - testMogakko.getMaxParticipants(), - testMogakko.getContent(), Collections.EMPTY_LIST)).id(); + new MogakkoCreateRequestDto(testCreator.getId(), testMogakko.getTitle(), + new LocationInfoDto("", 127.52d, 27.8621d, null, null), testMogakko.getStartTime(), + testMogakko.getEndTime(), testMogakko.getDeadline(), + testMogakko.getMaxParticipants(), + testMogakko.getContent(), Collections.EMPTY_LIST)).id(); testMogakko = mogakkoRepository.findById(savedMogakkoId) - .orElseThrow(() -> new RuntimeException("mogakko not found")); + .orElseThrow(() -> new RuntimeException("mogakko not found")); } @AfterEach @@ -106,7 +105,7 @@ void fail_participate_in_mogakko_already_belong_to() { // when then assertThatThrownBy(() -> mogakkoParticipationService.participate(mogakkoId, requestDto)) - .isInstanceOf(RuntimeException.class).hasMessageContaining("이미 참여한 유저입니다."); // TODO: 참여 예외 반환 + .isInstanceOf(RuntimeException.class).hasMessageContaining("이미 참여한 유저입니다."); // TODO: 참여 예외 반환 } @Test @@ -117,11 +116,11 @@ void fail_cancel_to_participate() { Long creatorId = testCreator.getId(); ThrowingCallable cancelFunc = () -> mogakkoParticipationService.cancel( - mogakkoId, creatorId); + mogakkoId, creatorId); // when then assertThatThrownBy(cancelFunc).isInstanceOf(RuntimeException.class) - .hasMessageContaining("모각코 생성자가 참여를 취소할 수 없습니다."); + .hasMessageContaining("모각코 생성자가 참여를 취소할 수 없습니다."); } @Test @@ -138,7 +137,7 @@ void fail_participate_in_mogakko_when_it_is_full() throws Exception { users.forEach(u -> imageRepository.save(u.getProfileImage())); userRepository.saveAll(users); List participationRequestDtos = users.stream() - .map(u -> new ParticipationRequestDto(u.getId(), null, null)).toList(); + .map(u -> new ParticipationRequestDto(u.getId(), null, null)).toList(); CountDownLatch countDownLatch = new CountDownLatch(usersNum); @@ -146,17 +145,17 @@ void fail_participate_in_mogakko_when_it_is_full() throws Exception { AtomicInteger overCount = new AtomicInteger(); participationRequestDtos.forEach(dto -> - threadPoolExecutor.execute( - () -> { - try { - mogakkoParticipationService.participate(mogakkoId, dto); - } catch (Exception e) { - log.info("예외 발생"); - overCount.getAndIncrement(); - } finally { - countDownLatch.countDown(); - } - }) + threadPoolExecutor.execute( + () -> { + try { + mogakkoParticipationService.participate(mogakkoId, dto); + } catch (Exception e) { + log.info("예외 발생"); + overCount.getAndIncrement(); + } finally { + countDownLatch.countDown(); + } + }) ); countDownLatch.await(5000, TimeUnit.MILLISECONDS); @@ -171,14 +170,14 @@ void success_update_info() { double latitude = 27.12345678999999d; double longitude = 127.12423d; ParticipationRequestDto requestDto = new ParticipationRequestDto(testCreator.getId(), - longitude, latitude); + longitude, latitude); // when mogakkoParticipationService.update(testMogakko.getId(), requestDto); // then Participant participant = participantRepository.findByMogakkoIdAndUserId( - testMogakko.getId(), testCreator.getId()).orElseThrow(RuntimeException::new); + testMogakko.getId(), testCreator.getId()).orElseThrow(RuntimeException::new); double columnPointLimit = 0.0000000001d; assertThat(participant.getLongitude()).isEqualTo(longitude, within(columnPointLimit)); @@ -212,7 +211,7 @@ void success_cancel_to_participate_in_mogakko() throws Exception { // then List participants = participantRepository.findAllByMogakkoId( - testMogakko.getId()); + testMogakko.getId()); assertThat(participants).hasSize(1); } @@ -222,9 +221,9 @@ void success_cancel_to_participate_in_mogakko() throws Exception { void success_check_whether_user_participate_in_mogakko() { // when ParticipationCheckingDto checkedTrueDto = mogakkoParticipationService.check(testMogakko.getId(), - testCreator.getId()); + testCreator.getId()); ParticipationCheckingDto checkedFalseDto = mogakkoParticipationService.check(testMogakko.getId(), - testCreator.getId() + 1L); + testCreator.getId() + 1L); // then assertThat(checkedTrueDto.isParticipated()).isTrue(); diff --git a/src/test/java/org/prgms/locomocoserver/user/application/UserServiceTest.java b/src/test/java/org/prgms/locomocoserver/user/application/UserServiceTest.java index c5d86959..16635d5d 100644 --- a/src/test/java/org/prgms/locomocoserver/user/application/UserServiceTest.java +++ b/src/test/java/org/prgms/locomocoserver/user/application/UserServiceTest.java @@ -211,7 +211,7 @@ void deleteUser_success() { .extracting(participant -> participant.getId()) .doesNotContain(user.getId()); - List chatRooms = chatRoomCustomRepository.findByParticipantsId(user.getId(), 0L, 10); + List chatRooms = chatRoomCustomRepository.findByParticipantsId(user.getId(), LocalDateTime.now().toString(), 10); assertThat(chatRooms).isEmpty(); }