Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,6 @@ List<Integer> findRoomIdsWithUserReplyByRoomIds(
@Param("adminRole") UserRole adminRole
);

@Query("""
SELECT m
FROM ChatMessage m
JOIN FETCH m.sender
WHERE m.id IN (
SELECT MAX(m2.id)
FROM ChatMessage m2
WHERE m2.chatRoom.id IN :roomIds
GROUP BY m2.chatRoom.id
)
""")
List<ChatMessage> findLatestMessagesByRoomIds(@Param("roomIds") List<Integer> roomIds);

@Query("SELECT cm FROM ChatMessage cm JOIN FETCH cm.chatRoom WHERE cm.id = :messageId")
Optional<ChatMessage> findByIdWithChatRoom(@Param("messageId") Integer messageId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package gg.agit.konect.domain.chat.service;

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

import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import gg.agit.konect.domain.chat.dto.ChatRoomSummaryResponse;
import gg.agit.konect.domain.chat.model.ChatRoomMember;
import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository;
import gg.agit.konect.domain.notification.enums.NotificationTargetType;
import gg.agit.konect.domain.notification.model.NotificationMuteSetting;
import gg.agit.konect.domain.notification.repository.NotificationMuteSettingRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatRoomSettingsService {

private final NotificationMuteSettingRepository notificationMuteSettingRepository;
private final ChatRoomMemberRepository chatRoomMemberRepository;

public List<ChatRoomSummaryResponse> applyUserSettings(
List<ChatRoomSummaryResponse> rooms,
Integer userId
) {
List<Integer> roomIds = rooms.stream()
.map(ChatRoomSummaryResponse::roomId)
.toList();
Map<Integer, Boolean> muteMap = getMuteMap(roomIds, userId);
Map<Integer, String> customRoomNameMap = getCustomRoomNameMap(roomIds, userId);

return rooms.stream()
.map(room -> applyRoomSettings(room, muteMap, customRoomNameMap))
.toList();
}

private ChatRoomSummaryResponse applyRoomSettings(
ChatRoomSummaryResponse room,
Map<Integer, Boolean> muteMap,
Map<Integer, String> customRoomNameMap
) {
return new ChatRoomSummaryResponse(
room.roomId(),
room.chatType(),
customRoomNameMap.getOrDefault(room.roomId(), room.roomName()),
room.roomImageUrl(),
room.lastMessage(),
room.lastSentAt(),
room.createdAt(),
room.unreadCount(),
muteMap.getOrDefault(room.roomId(), false)
);
}

private Map<Integer, Boolean> getMuteMap(List<Integer> roomIds, Integer userId) {
if (roomIds.isEmpty()) {
return Map.of();
}

List<NotificationMuteSetting> settings = notificationMuteSettingRepository
.findByTargetTypeAndTargetIdsAndUserId(NotificationTargetType.CHAT_ROOM, roomIds, userId);

Map<Integer, Boolean> muteMap = new HashMap<>();
for (NotificationMuteSetting setting : settings) {
Integer targetId = setting.getTargetId();
if (targetId != null) {
muteMap.put(targetId, setting.getIsMuted());
}
}

return muteMap;
}

private Map<Integer, String> getCustomRoomNameMap(List<Integer> roomIds, Integer userId) {
if (roomIds.isEmpty()) {
return Map.of();
}

return chatRoomMemberRepository.findByChatRoomIdsAndUserId(roomIds, userId).stream()
.filter(member -> StringUtils.hasText(member.getCustomRoomName()))
.collect(Collectors.toMap(ChatRoomMember::getChatRoomId, ChatRoomMember::getCustomRoomName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package gg.agit.konect.domain.chat.service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Service;

import gg.agit.konect.domain.chat.dto.ChatRoomSummaryResponse;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatRoomSummaryService {

private final ChatRoomSettingsService chatRoomSettingsService;

public List<ChatRoomSummaryResponse> summarizeChatRooms(
Integer userId,
List<ChatRoomSummaryResponse> directRooms,
List<ChatRoomSummaryResponse> clubRooms,
List<ChatRoomSummaryResponse> groupRooms
) {
List<ChatRoomSummaryResponse> rooms = new ArrayList<>();
rooms.addAll(directRooms);
rooms.addAll(clubRooms);
rooms.addAll(groupRooms);

rooms = new ArrayList<>(chatRoomSettingsService.applyUserSettings(rooms, userId));
rooms.sort(Comparator
.comparing(
(ChatRoomSummaryResponse room) ->
room.lastSentAt() != null ? room.lastSentAt() : room.createdAt(),
Comparator.reverseOrder()
));

return rooms;
}

public List<ChatRoomSummaryResponse> summarizeSearchableRooms(
Integer userId,
List<ChatRoomSummaryResponse> directRooms,
List<ChatRoomSummaryResponse> clubRooms
) {
List<ChatRoomSummaryResponse> rooms = new ArrayList<>();
rooms.addAll(directRooms);
rooms.addAll(clubRooms);

rooms = new ArrayList<>(chatRoomSettingsService.applyUserSettings(rooms, userId));
rooms.sort(
Comparator.comparing(ChatRoomSummaryResponse::lastSentAt,
Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(ChatRoomSummaryResponse::roomId)
);

return rooms;
}

public Map<Integer, String> getDefaultRoomNameMap(
List<ChatRoomSummaryResponse> directRooms,
List<ChatRoomSummaryResponse> clubRooms
) {
Map<Integer, String> defaultRoomNameMap = new HashMap<>();
directRooms.forEach(room -> defaultRoomNameMap.put(room.roomId(), room.roomName()));
clubRooms.forEach(room -> defaultRoomNameMap.put(room.roomId(), room.roomName()));
return defaultRoomNameMap;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package gg.agit.konect.domain.chat.service;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import gg.agit.konect.domain.chat.dto.ChatMessageMatchResult;
import gg.agit.konect.domain.chat.dto.ChatMessageMatchesResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomMatchesResponse;
import gg.agit.konect.domain.chat.dto.ChatRoomSummaryResponse;
import gg.agit.konect.domain.chat.dto.ChatSearchResponse;
import gg.agit.konect.domain.chat.enums.ChatType;
import gg.agit.konect.domain.chat.model.ChatMessage;
import gg.agit.konect.domain.chat.model.ChatRoomMember;
import gg.agit.konect.domain.chat.repository.ChatMessageQueryRepository;
import gg.agit.konect.domain.chat.repository.ChatRoomMemberRepository;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class ChatSearchService {

private final ChatMessageQueryRepository chatMessageQueryRepository;
private final ChatRoomMemberRepository chatRoomMemberRepository;

public ChatSearchResponse search(
Integer userId,
String keyword,
List<ChatRoomSummaryResponse> accessibleRooms,
Map<Integer, String> defaultRoomNameMap,
Integer page,
Integer limit
) {
String normalizedKeyword = normalizeKeyword(keyword);
ChatRoomMatchesResponse roomMatches = searchRoomsByName(
accessibleRooms,
defaultRoomNameMap,
normalizedKeyword,
page,
limit
);
ChatMessageMatchesResponse messageMatches = searchByMessageContent(
userId,
accessibleRooms,
normalizedKeyword,
page,
limit
);

return new ChatSearchResponse(roomMatches, messageMatches);
}

private ChatRoomMatchesResponse searchRoomsByName(
List<ChatRoomSummaryResponse> accessibleRooms,
Map<Integer, String> defaultRoomNameMap,
String keyword,
Integer page,
Integer limit
) {
List<ChatRoomSummaryResponse> matchedRooms = accessibleRooms.stream()
.filter(room -> matchesRoomName(room, keyword, defaultRoomNameMap))
.toList();

return ChatRoomMatchesResponse.from(toPage(matchedRooms, page, limit));
}

private ChatMessageMatchesResponse searchByMessageContent(
Integer userId,
List<ChatRoomSummaryResponse> accessibleRooms,
String keyword,
Integer page,
Integer limit
) {
if (accessibleRooms.isEmpty() || keyword.isBlank()) {
return ChatMessageMatchesResponse.from(emptyPage(page, limit));
}

Map<Integer, ChatRoomSummaryResponse> roomMap = accessibleRooms.stream()
.collect(Collectors.toMap(ChatRoomSummaryResponse::roomId, room -> room));
List<Integer> roomIds = accessibleRooms.stream()
.map(ChatRoomSummaryResponse::roomId)
.toList();
List<Integer> directRoomIds = accessibleRooms.stream()
.filter(room -> room.chatType() == ChatType.DIRECT)
.map(ChatRoomSummaryResponse::roomId)
.toList();
Map<Integer, LocalDateTime> visibleMessageFromMap = getVisibleMessageFromMap(directRoomIds, userId);

List<ChatMessageMatchResult> matchedMessages = chatMessageQueryRepository
.searchLatestMatchingMessagesByChatRoomIds(roomIds, keyword)
.stream()
.filter(message -> isVisibleMessageMatch(message, roomMap, visibleMessageFromMap))
.map(message -> ChatMessageMatchResult.from(roomMap.get(message.getChatRoom().getId()), message))
.toList();

return ChatMessageMatchesResponse.from(toPage(matchedMessages, page, limit));
}

private String normalizeKeyword(String keyword) {
return keyword == null ? "" : keyword.trim();
}

private boolean matchesRoomName(
ChatRoomSummaryResponse room,
String keyword,
Map<Integer, String> defaultRoomNameMap
) {
return containsKeyword(room.roomName(), keyword)
|| containsKeyword(defaultRoomNameMap.get(room.roomId()), keyword);
}

private boolean containsKeyword(String text, String keyword) {
return text != null
&& !keyword.isBlank()
&& text.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT));
}

private Map<Integer, LocalDateTime> getVisibleMessageFromMap(List<Integer> roomIds, Integer userId) {
if (roomIds.isEmpty()) {
return Map.of();
}

Map<Integer, LocalDateTime> visibleMessageFromMap = new HashMap<>();
for (ChatRoomMember roomMember : chatRoomMemberRepository.findByChatRoomIdsAndUserId(roomIds, userId)) {
visibleMessageFromMap.put(roomMember.getChatRoomId(), roomMember.getVisibleMessageFrom());
}
return visibleMessageFromMap;
}

private boolean isVisibleMessageMatch(
ChatMessage message,
Map<Integer, ChatRoomSummaryResponse> roomMap,
Map<Integer, LocalDateTime> visibleMessageFromMap
) {
ChatRoomSummaryResponse room = roomMap.get(message.getChatRoom().getId());
if (room == null || room.chatType() != ChatType.DIRECT) {
return true;
}

LocalDateTime visibleMessageFrom = visibleMessageFromMap.get(room.roomId());
return visibleMessageFrom == null || message.getCreatedAt().isAfter(visibleMessageFrom);
}

private <T> Page<T> toPage(List<T> items, Integer page, Integer limit) {
PageRequest pageable = PageRequest.of(page - 1, limit);
long offset = (long)(page - 1) * limit;
if (offset >= items.size()) {
return new PageImpl<>(List.of(), pageable, items.size());
}

int fromIndex = (int)offset;
int toIndex = Math.min(fromIndex + limit, items.size());
return new PageImpl<>(items.subList(fromIndex, toIndex), pageable, items.size());
}

private Page<ChatMessageMatchResult> emptyPage(Integer page, Integer limit) {
return new PageImpl<>(List.of(), PageRequest.of(page - 1, limit), 0);
}
}
Loading
Loading