Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ChatMessage extends BaseEntity {
@Column(nullable = false, length = 500)
private String content;

private long senderId;
private long senderId; // chat_participant의 id

@ManyToOne(fetch = FetchType.LAZY)
private ChatRoom chatRoom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
public record ChatMessageResponse(
long id,
String content,
long senderId,
long senderId, // siteUserId
ZonedDateTime createdAt,
List<ChatAttachmentResponse> attachments
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.example.solidconnection.chat.dto;

public record ChatParticipantResponse(
long partnerId,
long partnerId, // siteUserId
String nickname,
String profileUrl
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.example.solidconnection.chat.dto.ChatMessageSendRequest;
import com.example.solidconnection.chat.dto.ChatMessageSendResponse;
import com.example.solidconnection.chat.dto.ChatParticipantResponse;
import com.example.solidconnection.chat.dto.ChatRoomData;
import com.example.solidconnection.chat.dto.ChatRoomListResponse;
import com.example.solidconnection.chat.dto.ChatRoomResponse;
import com.example.solidconnection.chat.repository.ChatMessageRepository;
Expand All @@ -23,11 +24,15 @@
import com.example.solidconnection.chat.repository.ChatRoomRepository;
import com.example.solidconnection.common.dto.SliceResponse;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.chat.dto.ChatRoomData;
import com.example.solidconnection.mentor.repository.MentorRepository;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

사용되지 않는 MentorRepository 의존성이 주입되었습니다.

MentorRepository가 추가되었지만 코드 어디에서도 실제로 사용되지 않고 있습니다. PR 목표에서는 senderId가 멘토의 경우 mentorId, 멘티의 경우 siteUserId를 반환해야 한다고 명시되어 있는데, 현재 구현에서는 모든 경우에 siteUserId만 사용하고 있습니다(Line 151 참조).

다음 중 하나를 확인해 주세요:

  1. 이전 리뷰 논의에 따라 요구사항이 변경되어 모두 siteUserId를 사용하는 것이 맞다면, 사용하지 않는 MentorRepository 의존성을 제거해야 합니다.
  2. 멘토의 경우 mentorId를 반환해야 한다면, buildChatMessageResponses 메서드에서 mentorRepository를 활용하여 siteUserIdmentorId로 매핑하는 로직을 추가해야 합니다.

다음 스크립트를 실행하여 MentorRepository가 다른 곳에서 사용되는지 확인해 보세요:

Also applies to: 51-51, 60-60, 67-67


🏁 Script executed:

#!/bin/bash
# ChatService에서 mentorRepository 사용 여부 확인
rg -n "mentorRepository\." --type=java src/main/java/com/example/solidconnection/chat/service/ChatService.java

Length of output: 0


검증을 계속 진행하겠습니다. 현재 ChatService의 구현 내용을 확인하여 멘토레포지토리가 정말 사용되지 않는지, 그리고 요구사항이 실제로 변경되었는지 확인하겠습니다.




🏁 Script executed:

#!/bin/bash
# ChatService 전체 내용 확인 - 특히 buildChatMessageResponses 메서드와 Line 151 주변 코드
fd -n "ChatService.java" --type f | xargs cat

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Line 51, 60, 67 확인 - mentorRepository 필드 및 생성자
fd -n "ChatService.java" --type f | xargs sed -n '45,75p'

Length of output: 230


구문 오류를 수정하겠습니다. ChatService.java 파일을 올바르게 조회하겠습니다.


---

🏁 Script executed:

```shell
#!/bin/bash
# ChatService.java 찾기 및 전체 내용 표시
find . -name "ChatService.java" -type f | head -1 | xargs cat

Length of output: 13813


사용되지 않는 MentorRepository 의존성을 제거하거나 실제로 구현해야 합니다.

전체 코드를 검토한 결과, MentorRepository는 주입(Line 27, 51, 60, 67)만 되어 있고 어디에서도 실제로 사용되지 않고 있습니다.
buildChatMessageResponses 메서드(Line 141-157)에서도 senderParticipant.getSiteUserId()만 사용하고 있습니다.

다음 중 하나를 선택해 주세요:

  1. 요구사항이 변경되어 모든 경우 siteUserId를 사용하기로 결정되었다면
    MentorRepository import, 필드 선언, 생성자 파라미터 및 할당을 모두 제거하세요.

  2. 멘토의 경우 mentorId를 반환해야 한다면
    buildChatMessageResponses 메서드에서 senderParticipant 또는 역할 정보를 확인하여 mentorRepository를 활용해 siteUserIdmentorId로 매핑하는 로직을 추가하세요.

🤖 Prompt for AI Agents
In src/main/java/com/example/solidconnection/chat/service/ChatService.java
around lines 27, 51, 60, 67 and in buildChatMessageResponses (lines 141-157):
the MentorRepository is injected but never used; either remove the unused
MentorRepository import, field, constructor parameter and assignment (if
siteUserId is now always used), or implement the mentor-mapping path: add logic
in buildChatMessageResponses to detect mentor participants (via role or
senderParticipant), call mentorRepository to resolve siteUserId to mentorId, and
return mentorId for mentor messages; update imports and constructor to keep
mentorRepository only if you choose the mapping route.

import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
Expand All @@ -43,6 +48,7 @@ public class ChatService {
private final ChatParticipantRepository chatParticipantRepository;
private final ChatReadStatusRepository chatReadStatusRepository;
private final SiteUserRepository siteUserRepository;
private final MentorRepository mentorRepository;

private final SimpMessageSendingOperations simpMessageSendingOperations;

Expand All @@ -51,12 +57,14 @@ public ChatService(ChatRoomRepository chatRoomRepository,
ChatParticipantRepository chatParticipantRepository,
ChatReadStatusRepository chatReadStatusRepository,
SiteUserRepository siteUserRepository,
MentorRepository mentorRepository,
@Lazy SimpMessageSendingOperations simpMessageSendingOperations) {
this.chatRoomRepository = chatRoomRepository;
this.chatMessageRepository = chatMessageRepository;
this.chatParticipantRepository = chatParticipantRepository;
this.chatReadStatusRepository = chatReadStatusRepository;
this.siteUserRepository = siteUserRepository;
this.mentorRepository = mentorRepository;
this.simpMessageSendingOperations = simpMessageSendingOperations;
}

Expand Down Expand Up @@ -114,20 +122,46 @@ public SliceResponse<ChatMessageResponse> getChatMessages(long siteUserId, long

Slice<ChatMessage> chatMessages = chatMessageRepository.findByRoomIdWithPaging(roomId, pageable);

List<ChatMessageResponse> content = chatMessages.getContent().stream()
.map(this::toChatMessageResponse)
.toList();
Map<Long, ChatParticipant> participantIdToParticipant = buildParticipantIdToParticipantMap(chatMessages);
List<ChatMessageResponse> content = buildChatMessageResponses(chatMessages, participantIdToParticipant);

return SliceResponse.of(content, chatMessages);
}

// senderId(chatParticipantId)로 chatParticipant 맵 생성
private Map<Long, ChatParticipant> buildParticipantIdToParticipantMap(Slice<ChatMessage> chatMessages) {
Set<Long> participantIds = chatMessages.getContent().stream()
.map(ChatMessage::getSenderId)
.collect(Collectors.toSet());

return chatParticipantRepository.findAllById(participantIds).stream()
.collect(Collectors.toMap(ChatParticipant::getId, Function.identity()));
}

private List<ChatMessageResponse> buildChatMessageResponses(
Slice<ChatMessage> chatMessages,
Map<Long, ChatParticipant> participantIdToParticipant
) {
return chatMessages.getContent().stream()
.map(message -> {
ChatParticipant senderParticipant = participantIdToParticipant.get(message.getSenderId());
if (senderParticipant == null) {
throw new CustomException(CHAT_PARTICIPANT_NOT_FOUND);
}
long externalSenderId = senderParticipant.getSiteUserId();
return toChatMessageResponse(message, externalSenderId);
})
.toList();
}

@Transactional(readOnly = true)
public ChatParticipantResponse getChatPartner(long siteUserId, Long roomId) {
ChatRoom chatRoom = chatRoomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(INVALID_CHAT_ROOM_STATE));
ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId);
SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId())
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));

return ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl());
}

Expand All @@ -148,7 +182,7 @@ public void validateChatRoomParticipant(long siteUserId, long roomId) {
}
}

private ChatMessageResponse toChatMessageResponse(ChatMessage message) {
private ChatMessageResponse toChatMessageResponse(ChatMessage message, long externalSenderId) {
List<ChatAttachmentResponse> attachments = message.getChatAttachments().stream()
.map(attachment -> ChatAttachmentResponse.of(
attachment.getId(),
Expand All @@ -162,7 +196,7 @@ private ChatMessageResponse toChatMessageResponse(ChatMessage message) {
return ChatMessageResponse.of(
message.getId(),
message.getContent(),
message.getSenderId(),
externalSenderId,
message.getCreatedAt(),
attachments
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
import java.time.ZonedDateTime;

public record MentoringForMentorResponse(
long mentoringId,
Long roomId,
String profileImageUrl,
String nickname,
boolean isChecked,
VerifyStatus verifyStatus,
ZonedDateTime createdAt
) {

public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partner) {
public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partner, Long roomId) {
return new MentoringForMentorResponse(
mentoring.getId(),
roomId,
partner.getProfileImageUrl(),
partner.getNickname(),
mentoring.getCheckedAtByMentor() != null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import com.example.solidconnection.location.region.domain.Region;
import com.example.solidconnection.mentor.domain.Mentor;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -23,4 +25,6 @@ public interface MentorRepository extends JpaRepository<Mentor, Long> {
WHERE u.region = :region
""")
Slice<Mentor> findAllByRegion(@Param("region") Region region, Pageable pageable);

List<Mentor> findAllBySiteUserIdIn(Set<Long> siteUserIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,6 @@ public SliceResponse<MentoringForMenteeResponse> getMentoringsForMentee(
return SliceResponse.of(content, mentoringSlice);
}

// N+1 을 해결하면서 멘토링의 채팅방 정보 조회
private Map<Long, Long> mapMentoringIdToChatRoomIdWithBatchQuery(List<Mentoring> mentorings) {
List<Long> mentoringIds = mentorings.stream()
.map(Mentoring::getId)
.distinct()
.toList();
List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
return chatRooms.stream()
.collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId));
}

@Transactional(readOnly = true)
public SliceResponse<MentoringForMentorResponse> getMentoringsForMentor(long siteUserId, Pageable pageable) {
Mentor mentor = mentorRepository.findBySiteUserId(siteUserId)
Expand All @@ -140,9 +129,15 @@ public SliceResponse<MentoringForMentorResponse> getMentoringsForMentor(long sit
Mentoring::getMenteeId
);

Map<Long, Long> mentoringIdToChatRoomId = mapMentoringIdToChatRoomIdWithBatchQuery(mentoringSlice.getContent());

List<MentoringForMentorResponse> content = new ArrayList<>();
for (Mentoring mentoring : mentoringSlice) {
content.add(MentoringForMentorResponse.of(mentoring, mentoringToPartnerUser.get(mentoring)));
content.add(MentoringForMentorResponse.of(
mentoring,
mentoringToPartnerUser.get(mentoring),
mentoringIdToChatRoomId.get(mentoring.getId())
));
}

return SliceResponse.of(content, mentoringSlice);
Expand All @@ -165,4 +160,15 @@ private Map<Mentoring, SiteUser> mapMentoringToPartnerUserWithBatchQuery(
mentoring -> partnerIdToPartnerUsermap.get(getPartnerId.apply(mentoring))
));
}

// N+1 을 해결하면서 멘토링의 채팅방 정보 조회
private Map<Long, Long> mapMentoringIdToChatRoomIdWithBatchQuery(List<Mentoring> mentorings) {
List<Long> mentoringIds = mentorings.stream()
.map(Mentoring::getId)
.distinct()
.toList();
List<ChatRoom> chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds);
return chatRooms.stream()
.collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,18 @@ class 멘토의_멘토링_목록_조회_테스트 {
Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser2.getId());
Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor1.getId(), menteeUser3.getId());

ChatRoom chatRoom2 = chatRoomFixture.멘토링_채팅방(mentoring2.getId());

// when
SliceResponse<MentoringForMentorResponse> response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable);

// then
assertThat(response.content()).extracting(MentoringForMentorResponse::mentoringId)
assertThat(response.content())
.extracting(MentoringForMentorResponse::verifyStatus, MentoringForMentorResponse::roomId)
.containsExactlyInAnyOrder(
mentoring1.getId(),
mentoring2.getId(),
mentoring3.getId()
tuple(VerifyStatus.PENDING, null),
tuple(VerifyStatus.APPROVED, chatRoom2.getId()),
tuple(VerifyStatus.REJECTED, null)
);
}

Expand Down Expand Up @@ -137,10 +140,10 @@ class 멘토의_멘토링_목록_조회_테스트 {

// then
assertThat(response.content())
.extracting(MentoringForMentorResponse::mentoringId, MentoringForMentorResponse::isChecked)
.extracting(MentoringForMentorResponse::nickname, MentoringForMentorResponse::isChecked)
.containsExactlyInAnyOrder(
tuple(mentoring1.getId(), false),
tuple(mentoring2.getId(), true)
tuple(menteeUser1.getNickname(), false),
tuple(menteeUser2.getNickname(), true)
);
}

Expand Down
Loading