Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,57 @@
package gg.agit.konect.domain.chat.service;

import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS;

import java.time.LocalDateTime;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import gg.agit.konect.domain.chat.model.ChatRoom;
import gg.agit.konect.domain.chat.model.ChatRoomMember;
import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository;
import gg.agit.konect.domain.user.model.User;
import gg.agit.konect.global.exception.CustomException;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional
public class ChatDirectRoomAccessService {

private final ChatRoomMemberRepository chatRoomMemberRepository;

public ChatRoomMember getAccessibleMember(ChatRoom chatRoom, User user) {
ChatRoomMember member = getMember(chatRoom, user);
restoreIfVisible(member, chatRoom);
return member;
}

public LocalDateTime prepareAccessAndGetVisibleMessageFrom(ChatRoom chatRoom, User user) {
ChatRoomMember member = getMember(chatRoom, user);
LocalDateTime visibleMessageFrom = member.getVisibleMessageFrom();
restoreIfVisible(member, chatRoom);
return visibleMessageFrom;
}

private ChatRoomMember getMember(ChatRoom chatRoom, User user) {
return chatRoomMemberRepository.findByChatRoomIdAndUserId(chatRoom.getId(), user.getId())
.orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS));
}

/**
* direct 채팅방에서 나간 사용자가 다시 볼 수 있는 상태인지 확인하고,
* 새 메시지가 이미 존재하면 나간 상태를 해제한다.
*/
private void restoreIfVisible(ChatRoomMember member, ChatRoom chatRoom) {
if (!member.hasLeft()) {
return;
}

if (!member.hasVisibleMessages(chatRoom)) {
throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS);
}

member.restoreDirectRoom();
}
Comment thread
dh2906 marked this conversation as resolved.
}
56 changes: 8 additions & 48 deletions src/main/java/gg/agit/konect/domain/chat/service/ChatService.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class ChatService {
private final ChatSearchService chatSearchService;
private final ChatMessagePageResolver chatMessagePageResolver;
private final ChatRoomSystemAdminService chatRoomSystemAdminService;
private final ChatDirectRoomAccessService chatDirectRoomAccessService;
private final NotificationService notificationService;
private final ApplicationEventPublisher eventPublisher;

Expand Down Expand Up @@ -334,12 +335,12 @@ record SectionKey(Integer clubId, String clubName) {
return ChatInvitableUsersResponse.forClubSort(pagedInvitableUsers, sections);
}

@Transactional(readOnly = true)
@Transactional
public ChatMessagePageResponse getMessages(Integer userId, Integer roomId, Integer page, Integer limit) {
return getMessages(userId, roomId, page, limit, null);
}

@Transactional(readOnly = true)
@Transactional
public ChatMessagePageResponse getMessages(
Integer userId, Integer roomId, Integer page, Integer limit, Integer messageId
) {
Expand Down Expand Up @@ -410,7 +411,7 @@ public ChatMuteResponse toggleMute(Integer userId, Integer roomId) {
boolean isAdminAccessingSystemAdminRoom = user.isAdmin()
&& chatRoomSystemAdminService.isSystemAdminRoom(room.getId());
if (!isAdminAccessingSystemAdminRoom) {
getAccessibleDirectRoomMember(room, user);
chatDirectRoomAccessService.getAccessibleMember(room, user);
}
} else {
getAccessibleRoomMember(room, userId);
Expand Down Expand Up @@ -627,8 +628,8 @@ private ChatMessagePageResponse getDirectChatRoomMessages(
ChatRoom chatRoom = getDirectRoom(roomId);
User user = userRepository.getById(userId);
List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId);
LocalDateTime visibleMessageFrom = prepareDirectRoomAccess(getOrCreateDirectRoomMember(chatRoom, user),
chatRoom);
LocalDateTime visibleMessageFrom =
chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(chatRoom, user);

Comment thread
dh2906 marked this conversation as resolved.
List<LocalDateTime> sortedReadBaselines = toSortedReadBaselines(members);

Expand Down Expand Up @@ -666,12 +667,8 @@ private ChatMessageDetailResponse sendDirectMessage(
boolean isAdminSendingToSystemAdminRoom = sender.isAdmin()
&& chatRoomSystemAdminService.isSystemAdminRoom(chatRoom.getId());

ChatRoomMember senderMember = null;
boolean senderHadLeft = false;

if (!isAdminSendingToSystemAdminRoom) {
senderMember = getAccessibleDirectRoomMember(chatRoom, sender);
senderHadLeft = senderMember.hasLeft();
chatDirectRoomAccessService.getAccessibleMember(chatRoom, sender);
}

List<ChatRoomMember> members = chatRoomMemberRepository.findByChatRoomId(roomId);
Expand All @@ -681,10 +678,6 @@ private ChatMessageDetailResponse sendDirectMessage(
ChatMessage.of(chatRoom, sender, request.content())
);

if (senderHadLeft && senderMember != null) {
senderMember.restoreDirectRoom();
}

syncLastMessage(chatRoom, chatMessage);
members.stream()
.filter(member -> !member.getUserId().equals(userId))
Expand Down Expand Up @@ -994,7 +987,7 @@ private ChatRoomMember getAccessibleRoomMember(ChatRoom room, Integer userId) {

if (room.isDirectRoom()) {
User user = userRepository.getById(userId);
return getAccessibleDirectRoomMember(room, user);
return chatDirectRoomAccessService.getAccessibleMember(room, user);
}

ChatRoomMember member = getRoomMember(room.getId(), userId);
Expand Down Expand Up @@ -1147,44 +1140,11 @@ private Map<Integer, Integer> getRoomUnreadCountMap(List<Integer> roomIds, Integ
return unreadCountMap;
}

private ChatRoomMember getOrCreateDirectRoomMember(ChatRoom chatRoom, User user) {
return chatRoomMemberRepository.findByChatRoomIdAndUserId(chatRoom.getId(), user.getId())
.orElseThrow(() -> CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS));
}

private ChatRoomMember getAccessibleDirectRoomMember(ChatRoom chatRoom, User user) {
ChatRoomMember member = getOrCreateDirectRoomMember(chatRoom, user);
restoreDirectRoomIfVisible(member, chatRoom);
return member;
}

private LocalDateTime prepareDirectRoomAccess(ChatRoomMember member, ChatRoom chatRoom) {
LocalDateTime visibleMessageFrom = member.getVisibleMessageFrom();
restoreDirectRoomIfVisible(member, chatRoom);
return visibleMessageFrom;
}

private LocalDateTime resolveAdminSystemRoomVisibleMessageFrom(List<ChatRoomMember> members) {
ChatRoomMember systemAdminMember = chatRoomSystemAdminService.findSystemAdminMember(members);
return systemAdminMember != null ? systemAdminMember.getVisibleMessageFrom() : null;
}

/**
* direct 채팅방에서 나간 사용자가 다시 볼 수 있는 상태인지 확인하고,
* 새 메시지가 이미 존재하면 나간 상태를 해제한다.
*/
private void restoreDirectRoomIfVisible(ChatRoomMember member, ChatRoom chatRoom) {
if (!member.hasLeft()) {
return;
}

if (!member.hasVisibleMessages(chatRoom)) {
throw CustomException.of(FORBIDDEN_CHAT_ROOM_ACCESS);
}

member.restoreDirectRoom();
}

private boolean shouldDisplayAsOwnMessage(
User currentUser,
ChatMessage message,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package gg.agit.konect.unit.domain.chat.service;

import static gg.agit.konect.global.code.ApiResponseCode.FORBIDDEN_CHAT_ROOM_ACCESS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;

import java.time.LocalDateTime;
import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.test.util.ReflectionTestUtils;

import gg.agit.konect.domain.chat.model.ChatRoom;
import gg.agit.konect.domain.chat.model.ChatRoomMember;
import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository;
import gg.agit.konect.domain.chat.service.ChatDirectRoomAccessService;
import gg.agit.konect.domain.user.enums.UserRole;
import gg.agit.konect.domain.user.model.User;
import gg.agit.konect.global.exception.CustomException;
import gg.agit.konect.support.ServiceTestSupport;
import gg.agit.konect.support.fixture.UniversityFixture;
import gg.agit.konect.support.fixture.UserFixture;

class ChatDirectRoomAccessServiceTest extends ServiceTestSupport {

private static final LocalDateTime BASE_TIME = LocalDateTime.of(2026, 4, 27, 10, 0);

@Mock
private ChatRoomMemberRepository chatRoomMemberRepository;

@InjectMocks
private ChatDirectRoomAccessService chatDirectRoomAccessService;

@Test
@DisplayName("접근 가능한 direct room 멤버를 반환한다")
void getAccessibleMemberReturnsMember() {
User user = user(10);
ChatRoom room = room(1);
ChatRoomMember member = member(room, user);
given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()))
.willReturn(Optional.of(member));

ChatRoomMember result = chatDirectRoomAccessService.getAccessibleMember(room, user);

assertThat(result).isSameAs(member);
}

@Test
@DisplayName("나간 direct room에 새 메시지가 있으면 접근 시 나간 상태를 해제한다")
void getAccessibleMemberRestoresLeftMemberWhenVisibleMessageExists() {
User user = user(10);
ChatRoom room = room(1);
room.updateLastMessage("새 메시지", BASE_TIME.plusHours(2));
ChatRoomMember member = member(room, user);
member.leaveDirectRoom(BASE_TIME.plusHours(1));
given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()))
.willReturn(Optional.of(member));

chatDirectRoomAccessService.getAccessibleMember(room, user);

assertThat(member.hasLeft()).isFalse();
}

@Test
@DisplayName("나간 direct room에 새 메시지가 없으면 접근을 거부한다")
void getAccessibleMemberRejectsLeftMemberWithoutVisibleMessage() {
User user = user(10);
ChatRoom room = room(1);
ChatRoomMember member = member(room, user);
member.leaveDirectRoom(BASE_TIME.plusHours(1));
given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()))
.willReturn(Optional.of(member));

assertThatThrownBy(() -> chatDirectRoomAccessService.getAccessibleMember(room, user))
.isInstanceOf(CustomException.class)
.satisfies(exception ->
assertThat(((CustomException)exception).getErrorCode()).isEqualTo(FORBIDDEN_CHAT_ROOM_ACCESS));
}

@Test
@DisplayName("접근 준비는 복원 전 visibleMessageFrom을 반환한다")
void prepareAccessAndGetVisibleMessageFromReturnsPreviousVisibilityBoundary() {
User user = user(10);
ChatRoom room = room(1);
room.updateLastMessage("새 메시지", BASE_TIME.plusHours(2));
ChatRoomMember member = member(room, user);
LocalDateTime visibleMessageFrom = BASE_TIME.plusHours(1);
ReflectionTestUtils.setField(member, "leftAt", visibleMessageFrom);
ReflectionTestUtils.setField(member, "visibleMessageFrom", visibleMessageFrom);
given(chatRoomMemberRepository.findByChatRoomIdAndUserId(room.getId(), user.getId()))
.willReturn(Optional.of(member));

LocalDateTime result = chatDirectRoomAccessService.prepareAccessAndGetVisibleMessageFrom(room, user);

assertThat(result).isEqualTo(visibleMessageFrom);
assertThat(member.hasLeft()).isFalse();
}

private User user(Integer id) {
return UserFixture.createUserWithId(
UniversityFixture.createWithId(1),
id,
"사용자" + id,
"2024" + String.format("%04d", id),
UserRole.USER
);
}

private ChatRoom room(Integer id) {
ChatRoom room = ChatRoom.directOf();
ReflectionTestUtils.setField(room, "id", id);
ReflectionTestUtils.setField(room, "createdAt", BASE_TIME);
return room;
}

private ChatRoomMember member(ChatRoom room, User user) {
ChatRoomMember member = ChatRoomMember.of(room, user, BASE_TIME);
ReflectionTestUtils.setField(member, "createdAt", BASE_TIME);
return member;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@
import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository;
import gg.agit.konect.domain.chat.repository.ChatRoomQueryRepository;
import gg.agit.konect.domain.chat.repository.ChatRoomRepository;
import gg.agit.konect.domain.chat.service.ChatDirectRoomAccessService;
import gg.agit.konect.domain.chat.service.ChatMessagePageResolver;
import gg.agit.konect.domain.chat.service.ChatPresenceService;
import gg.agit.konect.domain.chat.service.ChatRoomMembershipService;
import gg.agit.konect.domain.chat.service.ChatRoomSummaryService;
import gg.agit.konect.domain.chat.service.ChatSearchService;
import gg.agit.konect.domain.chat.service.ChatMessagePageResolver;
import gg.agit.konect.domain.chat.service.ChatRoomSystemAdminService;
import gg.agit.konect.domain.chat.service.ChatService;
import gg.agit.konect.domain.club.model.Club;
Expand Down Expand Up @@ -123,13 +124,13 @@ class ChatServiceTest extends ServiceTestSupport {
@Mock
private ApplicationEventPublisher eventPublisher;

private ChatMessagePageResolver chatMessagePageResolver;

private ChatService chatService;

@BeforeEach
void setUp() {
chatMessagePageResolver = new ChatMessagePageResolver(
ChatDirectRoomAccessService chatDirectRoomAccessService =
new ChatDirectRoomAccessService(chatRoomMemberRepository);
ChatMessagePageResolver chatMessagePageResolver = new ChatMessagePageResolver(
chatMessageRepository,
chatRoomMemberRepository,
clubMemberRepository,
Expand All @@ -150,6 +151,7 @@ void setUp() {
chatSearchService,
chatMessagePageResolver,
chatRoomSystemAdminService,
chatDirectRoomAccessService,
notificationService,
eventPublisher
);
Expand Down
Loading