Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5ca89ea
refactor: 채팅방 생성 유효성 검증 로직 ChatRoom으로 이동
Dec 26, 2025
db36eae
refactor: 동아리 회장 변수 네이밍 수정
Dec 26, 2025
4c8f32d
feat: chat_room 테이블에 마지막으로 보낸 메시지 관련 컬럼 추가
Dec 26, 2025
61a5ef2
refactor: 채팅방 참여 인원 검증 메소드 네이밍 수정
Dec 26, 2025
64629f1
feat: 채팅 메시지를 보냈을 때, chat_room 테이블에 있는 채팅 메시지 관련 컬럼 업데이트 로직 추가
Dec 26, 2025
cf7fe70
refactor: ChatMessage 변수 인라인
Dec 26, 2025
32f8377
refactor: ChatMessage 변수 네이밍 수정
Dec 26, 2025
52f282d
refactor: getLastMessage 메소드 삭제 및 관련 메소드 내부 로직 수정
Dec 26, 2025
4adf457
refactor: 채팅방 참여자 유효성 검증 로직을 ChatRoom으로 이동
Dec 26, 2025
02e9fab
refactor: 채팅방 리스트를 조회할 때, LEFT JOIN으로 가져오는 메시지 로직 삭제 및 개선
Dec 26, 2025
17ad04f
refactor: 읽지 않는 메시지 개수 조회 메소드 네이밍 수정
Dec 26, 2025
f915093
refactor: 읽지 않는 메시지 리스트 조회 메소드 네이밍 수정
Dec 26, 2025
7c61840
fix: 채팅방 정렬 기준을 마지막으로 보낸 메시지 시각으로 수정
Dec 26, 2025
d187b46
refactor: POST /chats/rooms 요청 DTO 네이밍 수정
Dec 26, 2025
bc0d157
fix: ChatRoom의 OneToMany로 관리되는 ChatMessage 삭제
Dec 26, 2025
2604420
chore: ChatRoom 클래스 내부 메소드 순서 수정
Dec 26, 2025
e13db5a
fix: ChatRoom getter 메소드 삭제
Dec 26, 2025
fc19c07
fix: ChatMessage isSentBy 반환형 수정
Dec 26, 2025
3cfce0e
chore: 코드 개행
Dec 26, 2025
51bafc6
chore: ChatRoomService 클래스 네이밍 수정
Dec 26, 2025
6ee997b
chore: ChatMessage save 메소드 순서 수정
Dec 26, 2025
f3ddf6a
chore: 코드 개행
Dec 26, 2025
625e5be
refactor: 채팅방 정렬 기준 추가
Soundbar91 Dec 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import gg.agit.konect.domain.chat.dto.ChatMessagesResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomsResponse;
import gg.agit.konect.domain.chat.dto.CreateChatRoomRequest;
import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest;
import gg.agit.konect.global.auth.annotation.UserId;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -38,7 +38,7 @@ public interface ChatApi {
""")
@PostMapping("/rooms")
ResponseEntity<ChatRoomResponse> createOrGetChatRoom(
@Valid @RequestBody CreateChatRoomRequest request,
@Valid @RequestBody ChatRoomCreateRequest request,
@UserId Integer userId
);

Expand All @@ -52,7 +52,9 @@ ResponseEntity<ChatRoomResponse> createOrGetChatRoom(
- 최근 메시지가 있는 순서대로 정렬됩니다.
""")
@GetMapping("/rooms")
ResponseEntity<ChatRoomsResponse> getChatRooms(@UserId Integer userId);
ResponseEntity<ChatRoomsResponse> getChatRooms(
@UserId Integer userId
);

@Operation(summary = "문의하기 메시지 리스트를 조회한다.", description = """
## 설명
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import gg.agit.konect.domain.chat.dto.ChatMessagesResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomsResponse;
import gg.agit.konect.domain.chat.dto.CreateChatRoomRequest;
import gg.agit.konect.domain.chat.service.ChatRoomService;
import gg.agit.konect.domain.chat.dto.ChatRoomCreateRequest;
import gg.agit.konect.domain.chat.service.ChatService;
import gg.agit.konect.global.auth.annotation.UserId;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,20 +25,22 @@
@RequestMapping("/chats")
public class ChatController implements ChatApi {

private final ChatRoomService chatRoomService;
private final ChatService chatService;

@PostMapping("/rooms")
public ResponseEntity<ChatRoomResponse> createOrGetChatRoom(
@Valid @RequestBody CreateChatRoomRequest request,
@Valid @RequestBody ChatRoomCreateRequest request,
@UserId Integer userId
) {
ChatRoomResponse response = chatRoomService.createOrGetChatRoom(userId, request);
ChatRoomResponse response = chatService.createOrGetChatRoom(userId, request);
return ResponseEntity.ok(response);
}

@GetMapping("/rooms")
public ResponseEntity<ChatRoomsResponse> getChatRooms(@UserId Integer userId) {
ChatRoomsResponse response = chatRoomService.getChatRooms(userId);
public ResponseEntity<ChatRoomsResponse> getChatRooms(
@UserId Integer userId
) {
ChatRoomsResponse response = chatService.getChatRooms(userId);
return ResponseEntity.ok(response);
}

Expand All @@ -49,7 +51,7 @@ public ResponseEntity<ChatMessagesResponse> getChatRoomMessages(
@PathVariable(value = "chatRoomId") Integer chatRoomId,
@UserId Integer userId
) {
ChatMessagesResponse response = chatRoomService.getChatRoomMessages(userId, chatRoomId, page, limit);
ChatMessagesResponse response = chatService.getChatRoomMessages(userId, chatRoomId, page, limit);
return ResponseEntity.ok(response);
}

Expand All @@ -59,7 +61,7 @@ public ResponseEntity<ChatMessageResponse> sendMessage(
@Valid @RequestBody ChatMessageSendRequest request,
@UserId Integer userId
) {
ChatMessageResponse response = chatRoomService.sendMessage(userId, chatRoomId, request);
ChatMessageResponse response = chatService.sendMessage(userId, chatRoomId, request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record CreateChatRoomRequest(
public record ChatRoomCreateRequest(
@NotNull(message = "동아리 ID는 필수입니다.")
@Schema(description = "동아리 ID", example = "1", requiredMode = REQUIRED)
Integer clubId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonFormat;

Expand Down Expand Up @@ -36,23 +37,27 @@ public record InnerChatRoomResponse(
@Schema(description = "읽지 않은 메시지 개수", example = "12", requiredMode = REQUIRED)
Integer unreadCount
) {
public static InnerChatRoomResponse from(ChatRoom chatRoom, User currentUser) {
public static InnerChatRoomResponse from(
ChatRoom chatRoom, User currentUser, Map<Integer, Integer> unreadCountMap
) {
User chatPartner = chatRoom.getChatPartner(currentUser);

return new InnerChatRoomResponse(
chatRoom.getId(),
chatPartner.getName(),
chatPartner.getImageUrl(),
chatRoom.getLastMessageContent(),
chatRoom.getLastMessageTime(),
chatRoom.getUnreadCount(currentUser.getId())
chatRoom.getLastMessageSentAt(),
unreadCountMap.getOrDefault(chatRoom.getId(), 0)
);
}
}

public static ChatRoomsResponse from(List<ChatRoom> chatRooms, User currentUser) {
public static ChatRoomsResponse from(
List<ChatRoom> chatRooms, User currentUser, Map<Integer, Integer> unreadCountMap
) {
return new ChatRoomsResponse(chatRooms.stream()
.map(chatRoom -> InnerChatRoomResponse.from(chatRoom, currentUser))
.map(chatRoom -> InnerChatRoomResponse.from(chatRoom, currentUser, unreadCountMap))
.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gg.agit.konect.domain.chat.dto;

public record UnreadMessageCount(
Integer chatRoomId,
Long unreadCount
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static ChatMessage of(ChatRoom chatRoom, User sender, User receiver, Stri
.build();
}

public Boolean isSentBy(Integer userId) {
public boolean isSentBy(Integer userId) {
return sender.getId().equals(userId);
}

Expand Down
53 changes: 25 additions & 28 deletions src/main/java/gg/agit/konect/domain/chat/model/ChatRoom.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package gg.agit.konect.domain.chat.model;

import static gg.agit.konect.global.code.ApiResponseCode.CANNOT_CREATE_CHAT_ROOM_WITH_SELF;
import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS;
import static jakarta.persistence.FetchType.LAZY;
import static jakarta.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PROTECTED;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import gg.agit.konect.domain.user.model.User;
import gg.agit.konect.global.exception.CustomException;
import gg.agit.konect.global.model.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -34,6 +33,12 @@ public class ChatRoom extends BaseEntity {
@Column(name = "id", nullable = false, updatable = false, unique = true)
private Integer id;

@Column(name = "last_message_content", columnDefinition = "TEXT")
private String lastMessageContent;

@Column(name = "last_message_sent_at")
private LocalDateTime lastMessageSentAt;

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "sender_id")
private User sender;
Expand All @@ -42,9 +47,6 @@ public class ChatRoom extends BaseEntity {
@JoinColumn(name = "receiver_id")
private User receiver;

@OneToMany(mappedBy = "chatRoom", fetch = LAZY)
private List<ChatMessage> chatMessages = new ArrayList<>();

@Builder
private ChatRoom(Integer id, User sender, User receiver) {
this.id = id;
Expand All @@ -53,40 +55,35 @@ private ChatRoom(Integer id, User sender, User receiver) {
}

public static ChatRoom of(User sender, User receiver) {
validateIsNotSameParticipant(sender, receiver);
return ChatRoom.builder()
.sender(sender)
.receiver(receiver)
.build();
}

public User getChatPartner(User currentUser) {
return sender.getId().equals(currentUser.getId()) ? receiver : sender;
public static void validateIsNotSameParticipant(User sender, User receiver) {
if (sender.getId().equals(receiver.getId())) {
throw CustomException.of(CANNOT_CREATE_CHAT_ROOM_WITH_SELF);
}
}

public boolean isParticipant(Integer userId) {
return sender.getId().equals(userId) || receiver.getId().equals(userId);
public void validateIsParticipant(Integer userId) {
if (!isParticipant(userId)) {
throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS);
}
}

public ChatMessage getLastMessage() {
return chatMessages.stream()
.max(Comparator.comparing(BaseEntity::getCreatedAt))
.orElse(null);
}

public String getLastMessageContent() {
ChatMessage lastMessage = getLastMessage();
return lastMessage != null ? lastMessage.getContent() : null;
public boolean isParticipant(Integer userId) {
return sender.getId().equals(userId) || receiver.getId().equals(userId);
}

public LocalDateTime getLastMessageTime() {
ChatMessage lastMessage = getLastMessage();
return lastMessage != null ? lastMessage.getCreatedAt() : null;
public User getChatPartner(User currentUser) {
return sender.getId().equals(currentUser.getId()) ? receiver : sender;
}

public Integer getUnreadCount(Integer userId) {
return (int)chatMessages.stream()
.filter(message -> message.getReceiver().getId().equals(userId))
.filter(message -> !message.getIsRead())
.count();
public void updateLastMessage(String lastMessageContent, LocalDateTime lastMessageSentAt) {
this.lastMessageContent = lastMessageContent;
this.lastMessageSentAt = lastMessageSentAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import gg.agit.konect.domain.chat.dto.UnreadMessageCount;
import gg.agit.konect.domain.chat.model.ChatMessage;

public interface ChatMessageRepository extends Repository<ChatMessage, Integer> {

ChatMessage save(ChatMessage chatMessage);

@Query("""
SELECT new gg.agit.konect.domain.chat.dto.UnreadMessageCount(
cm.chatRoom.id,
COUNT(cm)
)
FROM ChatMessage cm
WHERE cm.chatRoom.id IN :chatRoomIds
AND cm.receiver.id = :receiverId
AND cm.isRead = false
GROUP BY cm.chatRoom.id
""")
List<UnreadMessageCount> countUnreadMessagesByChatRoomIdsAndUserId(
@Param("chatRoomIds") List<Integer> chatRoomIds,
@Param("receiverId") Integer receiverId
);

@Query("""
SELECT cm
FROM ChatMessage cm
Expand All @@ -31,7 +48,7 @@ public interface ChatMessageRepository extends Repository<ChatMessage, Integer>
AND cm.receiver.id = :receiverId
AND cm.isRead = false
""")
List<ChatMessage> findUnreadMessages(
List<ChatMessage> findUnreadMessagesByChatRoomIdAndUserId(
@Param("chatRoomId") Integer chatRoomId,
@Param("receiverId") Integer receiverId
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ public interface ChatRoomRepository extends Repository<ChatRoom, Integer> {
FROM ChatRoom cr
JOIN FETCH cr.sender
JOIN FETCH cr.receiver
LEFT JOIN FETCH cr.chatMessages
WHERE cr.sender.id = :userId OR cr.receiver.id = :userId
ORDER BY cr.updatedAt DESC
ORDER BY cr.lastMessageSentAt DESC NULLS LAST, cr.id
""")
List<ChatRoom> findByUserId(@Param("userId") Integer userId);

Expand Down
Loading