Skip to content

Commit

Permalink
Merge pull request #95 from max2023-4th-project-01/feat/chat-list
Browse files Browse the repository at this point in the history
feat: 채팅 목록 조회 기능 구현 #89
  • Loading branch information
sudago authored Oct 5, 2023
2 parents acd39de + 9a50a6c commit 0230f8c
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kr.codesquad.chat.dto.request.ChatMessageRequest;
import kr.codesquad.chat.dto.request.ChatRoomCreateRequest;
import kr.codesquad.chat.dto.request.SendMessageRequest;
import kr.codesquad.chat.dto.response.ChatRoomCreateResponse;
import kr.codesquad.chat.dto.response.ChatRoomListResponse;
import kr.codesquad.chat.service.ChatService;
import kr.codesquad.util.Constants;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
Expand All @@ -26,6 +26,13 @@ public class ChatController {

private final ChatService chatService;

@GetMapping("/api/chatrooms")
public ResponseEntity<ChatRoomListResponse> showAllUsersChatRooms(@RequestParam(required = false) Long itemId,
HttpServletRequest request) {
String loginId = (String)request.getAttribute(Constants.LOGIN_ID);
return ResponseEntity.ok().body(chatService.findChatRoomsBy(itemId, loginId));
}

@PostMapping("/api/chatrooms")
public ResponseEntity<ChatRoomCreateResponse> createChatRoom(
@RequestBody ChatRoomCreateRequest chatRoomCreateRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.codesquad.chat.dto.response;

import kr.codesquad.chat.dto.vo.ChatRoomListVo;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ChatMemberDataResponse {
private Long id;
private String profileImageUrl;
private String nickname;

public static ChatMemberDataResponse from(ChatRoomListVo chatRoomListVo) {
return ChatMemberDataResponse.builder().id(chatRoomListVo.getSenderId()).profileImageUrl(
chatRoomListVo.getSenderProfileImageUrl()).nickname(chatRoomListVo.getSenderNickname()).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.codesquad.chat.dto.response;

import java.util.List;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ChatRoomListResponse {
private final List<UserChatRoomListResponse> chatRooms;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.codesquad.chat.dto.response;

import kr.codesquad.chat.dto.vo.ChatRoomListVo;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ItemDataResponse {
private Long id;
private String thumbnailUrl;

public static ItemDataResponse from(ChatRoomListVo chatRoomListVo) {
return ItemDataResponse.builder().id(chatRoomListVo.getItemId()).thumbnailUrl(
chatRoomListVo.getItemThumbnailUrl()).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package kr.codesquad.chat.dto.response;

import java.time.ZonedDateTime;
import java.util.Map;

import kr.codesquad.chat.dto.vo.ChatRoomListVo;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class UserChatRoomListResponse {
private Long id;
private ChatMemberDataResponse chatMember;
private String recentMessage;
private int unreadCount;
private ZonedDateTime updatedAt;
private ItemDataResponse item;

public static UserChatRoomListResponse of(ChatRoomListVo chatRoom, Map<Long, Integer> unreadCounts) {
return UserChatRoomListResponse.builder()
.id(chatRoom.getId())
.chatMember(ChatMemberDataResponse.from(chatRoom))
.recentMessage(chatRoom.getRecentMessage())
.unreadCount(unreadCounts.getOrDefault(chatRoom.getId(), 0))
.updatedAt(chatRoom.getUpdatedAt())
.item(ItemDataResponse.from(chatRoom))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.codesquad.chat.dto.vo;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ChatMessageCountDataVo {
private Long chatRoomId;
private boolean isRead;
private Long senderId;

@Builder
public ChatMessageCountDataVo(Long chatRoomId, boolean isRead, Long senderId) {
this.chatRoomId = chatRoomId;
this.isRead = isRead;
this.senderId = senderId;
}
}
36 changes: 36 additions & 0 deletions be/src/main/java/kr/codesquad/chat/dto/vo/ChatRoomListVo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kr.codesquad.chat.dto.vo;

import java.time.ZonedDateTime;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ChatRoomListVo {
private Long id;
private Long senderId;
private String senderProfileImageUrl;
private String senderNickname;
private String recentMessage;
private int unreadCount;
private ZonedDateTime updatedAt;
private Long itemId;
private String itemThumbnailUrl;

@Builder
public ChatRoomListVo(Long id, Long senderId, String senderProfileImageUrl, String senderNickname,
String recentMessage,
int unreadCount, ZonedDateTime updatedAt, Long itemId, String itemThumbnailUrl) {
this.id = id;
this.senderId = senderId;
this.senderProfileImageUrl = senderProfileImageUrl;
this.senderNickname = senderNickname;
this.recentMessage = recentMessage;
this.unreadCount = unreadCount;
this.updatedAt = updatedAt;
this.itemId = itemId;
this.itemThumbnailUrl = itemThumbnailUrl;
}
}
12 changes: 11 additions & 1 deletion be/src/main/java/kr/codesquad/chat/entity/ChatMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,21 @@ public class ChatMessage extends TimeStamped {
private Long chatRoomId;
@Column(nullable = false, length = 200)
private String content;
@Column(columnDefinition = "BIT DEFAULT 0")
private boolean isRead;
@Column(nullable = false)
private Long senderId;

@Builder
public ChatMessage(Long id, Long chatRoomId, String content) {
public ChatMessage(Long id, Long chatRoomId, String content, boolean isRead, Long senderId) {
this.id = id;
this.chatRoomId = chatRoomId;
this.content = content;
this.isRead = isRead;
this.senderId = senderId;
}

public void readMessage() {
this.isRead = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package kr.codesquad.chat.repository;

import static kr.codesquad.chat.entity.QChatMessage.*;
import static kr.codesquad.chat.entity.QChatRoom.*;
import static kr.codesquad.item.entity.QItem.*;
import static kr.codesquad.user.entity.QUser.*;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;

import kr.codesquad.chat.dto.vo.ChatMessageCountDataVo;
import kr.codesquad.chat.dto.vo.ChatRoomListVo;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Repository
public class ChatRoomListRepository {
private final JPAQueryFactory queryFactory;

public List<ChatRoomListVo> findAllBy(Long itemId, Long userId) {
return queryFactory
.select(Projections.fields(ChatRoomListVo.class,
chatRoom.id,
user.id.as("senderId"),
user.profileImageUrl.as("senderProfileImageUrl"),
user.nickname.as("senderNickname"),
item.id.as("itemId"),
item.thumbnailUrl.as("itemThumbnailUrl"),
chatMessage.content.as("recentMessage"),
chatMessage.createdAt.as("updatedAt")
))
.from(chatRoom)
.leftJoin(user).on(chatRoom.senderId.eq(user.id))
.leftJoin(item).on(chatRoom.itemId.eq(item.id))
.leftJoin(chatMessage).on(
chatMessage.chatRoomId.eq(chatRoom.id)
.and(chatMessage.createdAt.eq(
JPAExpressions
.select(chatMessage.createdAt.max())
.from(chatMessage)
.where(chatMessage.chatRoomId.eq(chatRoom.id))
))
)
.where(item.userId.eq(userId).or(chatRoom.senderId.eq(userId)), equalItemId(itemId)).fetch();
}

public List<ChatMessageCountDataVo> countUnreadChatMessage(Long userId) {
return queryFactory
.select(Projections.fields(ChatMessageCountDataVo.class,
chatRoom.id.as("chatRoomId"),
chatMessage.isRead,
chatMessage.senderId
))
.from(chatRoom)
.leftJoin(item).on(chatRoom.itemId.eq(item.id))
.leftJoin(chatMessage).on(chatMessage.chatRoomId.eq(chatRoom.id))
.where(chatRoom.senderId.eq(userId).or(item.userId.eq(userId)), chatMessage.isRead.eq(false))
.fetch();
}

private BooleanExpression equalItemId(Long itemId) {
if (itemId == null) {
return null;
}

return item.id.eq(itemId);
}

private BooleanExpression equalSenderId(Long userId) {
return chatRoom.senderId.eq(userId);
}

}
48 changes: 45 additions & 3 deletions be/src/main/java/kr/codesquad/chat/service/ChatService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package kr.codesquad.chat.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Service;
Expand All @@ -10,10 +15,14 @@
import kr.codesquad.chat.dto.ChatMapper;
import kr.codesquad.chat.dto.request.ChatMessageRequest;
import kr.codesquad.chat.dto.request.ChatRoomCreateRequest;
import kr.codesquad.chat.dto.request.SendMessageRequest;
import kr.codesquad.chat.dto.response.ChatRoomCreateResponse;
import kr.codesquad.chat.dto.response.ChatRoomListResponse;
import kr.codesquad.chat.dto.response.UserChatRoomListResponse;
import kr.codesquad.chat.dto.vo.ChatMessageCountDataVo;
import kr.codesquad.chat.dto.vo.ChatRoomListVo;
import kr.codesquad.chat.entity.ChatMessage;
import kr.codesquad.chat.repository.ChatMessageRepository;
import kr.codesquad.chat.repository.ChatRoomListRepository;
import kr.codesquad.chat.repository.ChatRoomRepository;
import kr.codesquad.user.entity.User;
import kr.codesquad.user.repository.UserRepository;
Expand All @@ -31,6 +40,7 @@ public class ChatService {
private final ObjectMapper objectMapper;
private final ChannelTopic channelTopic;
private final RedisTemplate redisTemplate;
private final ChatRoomListRepository chatRoomListRepository;

public ChatRoomCreateResponse createRoom(ChatRoomCreateRequest chatRoomCreateRequest, String loginId) {
User user = userRepository.findByLoginId(loginId);
Expand All @@ -42,9 +52,41 @@ public ChatRoomCreateResponse createRoom(ChatRoomCreateRequest chatRoomCreateReq
public void sendMessage(ChatMessageRequest chatMessageRequest, Long chatRoomId) {

//채팅 생성 및 저장
ChatMessage chatMessage = chatMessageRepository.save(ChatMapper.INSTANCE.toChatMessage(chatMessageRequest, chatRoomId));
// TODO: 채팅방에 사람이 있다면 ChatMessage 읽음 처리 로직 추가
ChatMessage chatMessage = chatMessageRepository.save(
ChatMapper.INSTANCE.toChatMessage(chatMessageRequest, chatRoomId));
String topic = channelTopic.getTopic();

redisTemplate.convertAndSend(topic, ChatMapper.INSTANCE.toSendMessageRequest(chatMessage, chatMessageRequest.getSenderId()));
redisTemplate.convertAndSend(topic,
ChatMapper.INSTANCE.toSendMessageRequest(chatMessage, chatMessageRequest.getSenderId()));
}

public ChatRoomListResponse findChatRoomsBy(Long itemId, String loginId) {
Long userId = userRepository.findByLoginId(loginId).getId();
List<ChatRoomListVo> chatRoomListVos = chatRoomListRepository.findAllBy(itemId, userId);
List<ChatMessageCountDataVo> chatMessageCountDataVos = chatRoomListRepository.countUnreadChatMessage(
userId); // unreadCount만 따로 조회

Map<Long, Integer> unreadChatMessageCountMap = countUnreadChatMessage(chatMessageCountDataVos, userId);

// chatRoomListVos에 맞는 unreadCount를 넣어줌.
List<UserChatRoomListResponse> userChatRoomListResponses = chatRoomListVos.stream()
.map(chatRoom -> UserChatRoomListResponse.of(chatRoom, unreadChatMessageCountMap))
.collect(
Collectors.toUnmodifiableList());

return ChatRoomListResponse.builder().chatRooms(userChatRoomListResponses).build();
}

private Map<Long, Integer> countUnreadChatMessage(List<ChatMessageCountDataVo> chatMessageCountDataVos,
Long userId) {
Map<Long, Integer> map = new HashMap<>();
for (ChatMessageCountDataVo chatMessageCountDataVo : chatMessageCountDataVos) {
if (chatMessageCountDataVo.getSenderId() != userId && !chatMessageCountDataVo.isRead()) {
map.put(chatMessageCountDataVo.getChatRoomId(),
map.getOrDefault(chatMessageCountDataVo.getChatRoomId(), 0) + 1);
}
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/api/ws")
.setAllowedOriginPatterns("*");
.setAllowedOriginPatterns("*").withSockJS();
}

@Override
Expand Down

0 comments on commit 0230f8c

Please sign in to comment.