Compare commits
No commits in common. "0d6a4ee8dd170c4d22bcb512742b537d1489e7a3" and "3462d827164859d9cbf2a32b47fbbf744ceca6be" have entirely different histories.
0d6a4ee8dd
...
3462d82716
|
|
@ -1,13 +1,10 @@
|
||||||
package com.alttd.altitudeweb.controllers.event;
|
package com.alttd.altitudeweb.controllers.event;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper;
|
import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper;
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
|
||||||
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
|
|
||||||
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
||||||
import com.alttd.altitudeweb.services.chat.ChatService;
|
import com.alttd.altitudeweb.services.chat.ChatService;
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -24,9 +21,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
@ -67,29 +62,7 @@ public class EventController {
|
||||||
.map(ChatMessageMapper::toJson)
|
.map(ChatMessageMapper::toJson)
|
||||||
.collect(Collectors.joining(",", "[", "]"));
|
.collect(Collectors.joining(",", "[", "]"));
|
||||||
|
|
||||||
Integer partyId = getPartyId(subject);
|
return eventPublisher.subscribe(new EventUser(subject, authorities), json);
|
||||||
return eventPublisher.subscribe(new EventUser(subject, authorities, partyId), json);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer getPartyId(UUID subject) {
|
|
||||||
CompletableFuture<Integer> partyIdFuture = new CompletableFuture<>();
|
|
||||||
|
|
||||||
Connection.getConnection(Databases.CHAT)
|
|
||||||
.runQuery(sqlSession -> {
|
|
||||||
log.debug("Loading party id for uuid {}", subject);
|
|
||||||
try {
|
|
||||||
Integer partyId = sqlSession.getMapper(ChatLogMapper.class)
|
|
||||||
.getPartyId(subject.toString())
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
partyIdFuture.complete(partyId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to load party id", e);
|
|
||||||
partyIdFuture.complete(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return partyIdFuture.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean doesNotHavePermission(List<String> authorities) {
|
private static boolean doesNotHavePermission(List<String> authorities) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import com.alttd.altitudeweb.database.Databases;
|
||||||
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
|
import com.alttd.altitudeweb.database.chat.ChatLogMapper;
|
||||||
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -84,58 +83,23 @@ public class ChatService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessagesToListeners(List<ChatMessage> chatMessageList) {
|
private void sendMessagesToListeners(List<ChatMessage> chatMessageList) {
|
||||||
Map<ChatMessage, String> jsonCache = new HashMap<>();
|
String jsonMessageListIncludingBlocked = chatMessageList.stream()
|
||||||
for (ChatMessage message : chatMessageList) {
|
.map(ChatMessageMapper::toJson)
|
||||||
jsonCache.put(message, ChatMessageMapper.toJson(message));
|
.collect(Collectors.joining(",", "[", "]"));
|
||||||
}
|
String jsonMessageList = chatMessageList.stream()
|
||||||
|
.filter(ChatMessage::isNotBlocked)
|
||||||
|
.filter(chatMessage -> Arrays.asList(allowedServers).contains(chatMessage.getServer()))
|
||||||
|
//TODO [Stijn] [2026-07-19]: Handle different channel types
|
||||||
|
.filter(chatMessage -> chatMessage.getType() == ChatMessageType.PUBLIC)
|
||||||
|
.map(ChatMessageMapper::toJson)
|
||||||
|
.collect(Collectors.joining(",", "[", "]"));
|
||||||
|
|
||||||
eventPublisher.sendToUsers("chat", (eventUser) ->
|
eventPublisher.sendToUsers("chat", (eventUser) -> {
|
||||||
chatMessageList.stream()
|
if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) {
|
||||||
.filter(chatMessage -> shouldReceive(eventUser, chatMessage))
|
return jsonMessageListIncludingBlocked;
|
||||||
.map(jsonCache::get)
|
|
||||||
.collect(Collectors.joining(",", "[", "]"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldReceive(EventUser eventUser, ChatMessage chatMessage) {
|
|
||||||
if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Arrays.asList(allowedServers).contains(chatMessage.getServer())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chatMessage.isNotBlocked()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (chatMessage.getType()) {
|
|
||||||
case PUBLIC, GLOBAL -> true;
|
|
||||||
case PARTY -> {
|
|
||||||
Integer partyId = eventUser.partyId();
|
|
||||||
if (partyId == null) {
|
|
||||||
yield false;
|
|
||||||
}
|
|
||||||
yield chatMessage.getChannel().equals(String.valueOf(partyId));
|
|
||||||
}
|
}
|
||||||
case MSG -> {
|
return jsonMessageList;
|
||||||
if (eventUser.uuid().equals(chatMessage.getUuid())) {
|
});
|
||||||
yield true;
|
|
||||||
}
|
|
||||||
if (chatMessage.getReceiver() != null) {
|
|
||||||
try {
|
|
||||||
UUID receiverUuid = UUID.fromString(chatMessage.getReceiver());
|
|
||||||
yield eventUser.uuid().equals(receiverUuid);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
yield false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield false;
|
|
||||||
}
|
|
||||||
case GAC -> eventUser.hasPermission(PermissionClaimDto.MOD);
|
|
||||||
case CUSTOM -> false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ChatMessage> getMessagesSince(Instant instant) {
|
public List<ChatMessage> getMessagesSince(Instant instant) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public record EventUser(UUID uuid, List<String> authorities, Integer partyId) {
|
public record EventUser(UUID uuid, List<String> authorities) {
|
||||||
|
|
||||||
public boolean hasPermission(PermissionClaimDto permission) {
|
public boolean hasPermission(PermissionClaimDto permission) {
|
||||||
return authorities.contains(permission.getValue());
|
return authorities.contains(permission.getValue());
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
package com.alttd.altitudeweb.services.chat;
|
|
||||||
|
|
||||||
import com.alttd.altitudeweb.controllers.chat.ChatMessage;
|
|
||||||
import com.alttd.altitudeweb.controllers.chat.ChatMessageType;
|
|
||||||
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventPublisher;
|
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.EventUser;
|
|
||||||
import com.alttd.altitudeweb.services.chat.event_publisher.MessageForUser;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
class ChatServiceTest {
|
|
||||||
|
|
||||||
private ChatService chatService;
|
|
||||||
private EventPublisher eventPublisher;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
eventPublisher = mock(EventPublisher.class);
|
|
||||||
chatService = new ChatService(eventPublisher);
|
|
||||||
ReflectionTestUtils.setField(chatService, "allowedServers", new String[]{"server1"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testPublicMessageSentToEveryone() {
|
|
||||||
ChatMessage message = ChatMessage.builder()
|
|
||||||
.uuid(UUID.randomUUID())
|
|
||||||
.type(ChatMessageType.PUBLIC)
|
|
||||||
.server("server1")
|
|
||||||
.messageJson("hello")
|
|
||||||
.notBlocked(true)
|
|
||||||
.timestamp(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chatService.addChatMessage(List.of(message));
|
|
||||||
|
|
||||||
ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
|
|
||||||
verify(eventPublisher).sendToUsers(anyString(), captor.capture());
|
|
||||||
|
|
||||||
MessageForUser messageForUser = captor.getValue();
|
|
||||||
EventUser user = new EventUser(UUID.randomUUID(), List.of());
|
|
||||||
String result = messageForUser.get(user);
|
|
||||||
|
|
||||||
assertTrue(result.contains("hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGacMessageSentOnlyToStaff() {
|
|
||||||
ChatMessage message = ChatMessage.builder()
|
|
||||||
.uuid(UUID.randomUUID())
|
|
||||||
.type(ChatMessageType.GAC)
|
|
||||||
.server("server1")
|
|
||||||
.messageJson("staff chat")
|
|
||||||
.notBlocked(true)
|
|
||||||
.timestamp(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chatService.addChatMessage(List.of(message));
|
|
||||||
|
|
||||||
ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
|
|
||||||
verify(eventPublisher).sendToUsers(anyString(), captor.capture());
|
|
||||||
|
|
||||||
MessageForUser messageForUser = captor.getValue();
|
|
||||||
|
|
||||||
EventUser regularUser = new EventUser(UUID.randomUUID(), List.of());
|
|
||||||
assertFalse(messageForUser.get(regularUser).contains("staff chat"), "Regular user should not see GAC message");
|
|
||||||
|
|
||||||
EventUser modUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.MOD.getValue()));
|
|
||||||
assertTrue(messageForUser.get(modUser).contains("staff chat"), "MOD should see GAC message");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMsgMessageSentToSenderAndReceiver() {
|
|
||||||
UUID senderUuid = UUID.randomUUID();
|
|
||||||
UUID receiverUuid = UUID.randomUUID();
|
|
||||||
ChatMessage message = ChatMessage.builder()
|
|
||||||
.uuid(senderUuid)
|
|
||||||
.type(ChatMessageType.MSG)
|
|
||||||
.server("server1")
|
|
||||||
.receiver(receiverUuid.toString())
|
|
||||||
.messageJson("private message")
|
|
||||||
.notBlocked(true)
|
|
||||||
.timestamp(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chatService.addChatMessage(List.of(message));
|
|
||||||
|
|
||||||
ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
|
|
||||||
verify(eventPublisher).sendToUsers(anyString(), captor.capture());
|
|
||||||
|
|
||||||
MessageForUser messageForUser = captor.getValue();
|
|
||||||
|
|
||||||
EventUser sender = new EventUser(senderUuid, List.of());
|
|
||||||
EventUser receiver = new EventUser(receiverUuid, List.of());
|
|
||||||
EventUser other = new EventUser(UUID.randomUUID(), List.of());
|
|
||||||
|
|
||||||
assertTrue(messageForUser.get(sender).contains("private message"), "Sender should see their own message");
|
|
||||||
assertTrue(messageForUser.get(receiver).contains("private message"), "Receiver should see the message");
|
|
||||||
assertFalse(messageForUser.get(other).contains("private message"), "Others should not see private message");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGlobalMessageSentToEveryone() {
|
|
||||||
ChatMessage message = ChatMessage.builder()
|
|
||||||
.uuid(UUID.randomUUID())
|
|
||||||
.type(ChatMessageType.GLOBAL)
|
|
||||||
.server("server1")
|
|
||||||
.messageJson("global hello")
|
|
||||||
.notBlocked(true)
|
|
||||||
.timestamp(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chatService.addChatMessage(List.of(message));
|
|
||||||
|
|
||||||
ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
|
|
||||||
verify(eventPublisher).sendToUsers(anyString(), captor.capture());
|
|
||||||
|
|
||||||
MessageForUser messageForUser = captor.getValue();
|
|
||||||
EventUser user = new EventUser(UUID.randomUUID(), List.of());
|
|
||||||
assertTrue(messageForUser.get(user).contains("global hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testBlockedMessageOnlySentToHeadMod() {
|
|
||||||
ChatMessage message = ChatMessage.builder()
|
|
||||||
.uuid(UUID.randomUUID())
|
|
||||||
.type(ChatMessageType.PUBLIC)
|
|
||||||
.server("server1")
|
|
||||||
.messageJson("blocked message")
|
|
||||||
.notBlocked(false)
|
|
||||||
.timestamp(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chatService.addChatMessage(List.of(message));
|
|
||||||
|
|
||||||
ArgumentCaptor<MessageForUser> captor = ArgumentCaptor.forClass(MessageForUser.class);
|
|
||||||
verify(eventPublisher).sendToUsers(anyString(), captor.capture());
|
|
||||||
|
|
||||||
MessageForUser messageForUser = captor.getValue();
|
|
||||||
EventUser regularUser = new EventUser(UUID.randomUUID(), List.of());
|
|
||||||
EventUser headModUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.HEAD_MOD.getValue()));
|
|
||||||
|
|
||||||
assertFalse(messageForUser.get(regularUser).contains("blocked message"));
|
|
||||||
assertTrue(messageForUser.get(headModUser).contains("blocked message"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,6 @@ import org.apache.ibatis.annotations.Param;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface ChatLogMapper {
|
public interface ChatLogMapper {
|
||||||
|
|
||||||
|
|
@ -16,10 +15,4 @@ public interface ChatLogMapper {
|
||||||
""")
|
""")
|
||||||
List<ChatLogDao> getChatLogs(@Param("since") long since);
|
List<ChatLogDao> getChatLogs(@Param("since") long since);
|
||||||
|
|
||||||
@Select("""
|
|
||||||
SELECT party_id FROM chat_users
|
|
||||||
WHERE uuid = #{uuid}
|
|
||||||
""")
|
|
||||||
Optional<Integer> getPartyId(@Param("uuid") String uuid);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user