diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ChatController.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ChatController.java index f69dab7..ddf3b96 100644 --- a/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ChatController.java +++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ChatController.java @@ -28,7 +28,7 @@ public class ChatController implements ChatApi { @Override public ResponseEntity updateServerStates(ServerStateDto serverStateDto) { - //TODO [Stijn] [2026-07-18]: Implement handling server state updates (push to listeners) - throw new UnsupportedOperationException("Not implemented"); + chatService.updateServerState(serverStateDto); + return new ResponseEntity<>(HttpStatus.ACCEPTED); } } diff --git a/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ServerStateMapper.java b/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ServerStateMapper.java new file mode 100644 index 0000000..bc24be9 --- /dev/null +++ b/backend/src/main/java/com/alttd/altitudeweb/controllers/chat/ServerStateMapper.java @@ -0,0 +1,24 @@ +package com.alttd.altitudeweb.controllers.chat; + +import com.alttd.altitudeweb.model.ServerStateDto; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class ServerStateMapper { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS); + + public static String toJson(ServerStateDto serverStateDto) { + try { + return OBJECT_MAPPER.writeValueAsString(serverStateDto); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to serialize ServerStateDto to JSON", e); + } + } +} diff --git a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java index e7cf06e..46a0e4a 100644 --- a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java +++ b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java @@ -2,11 +2,14 @@ package com.alttd.altitudeweb.services.chat; import com.alttd.altitudeweb.controllers.chat.ChatMessage; import com.alttd.altitudeweb.controllers.chat.ChatMessageMapper; +import com.alttd.altitudeweb.controllers.chat.ServerStateMapper; import com.alttd.altitudeweb.database.Databases; import com.alttd.altitudeweb.database.chat.ChatLogMapper; import com.alttd.altitudeweb.database.web_db.chat_session.ChatSession; import com.alttd.altitudeweb.database.web_db.chat_session.ChatSessionMapper; import com.alttd.altitudeweb.model.PermissionClaimDto; +import com.alttd.altitudeweb.model.ServerDto; +import com.alttd.altitudeweb.model.ServerStateDto; 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; @@ -121,6 +124,20 @@ public class ChatService { sendMessagesToListeners(chatMessageList); } + public void updateServerState(ServerStateDto serverStateDto) { + sendToUsers("server-state", (eventUser) -> { + if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) { + return ServerStateMapper.toJson(serverStateDto); + } + + List filteredServers = serverStateDto.getServers().stream() + .filter(serverDto -> Arrays.asList(allowedServers).contains(serverDto.getName())) + .toList(); + + return ServerStateMapper.toJson(new ServerStateDto().servers(filteredServers)); + }); + } + private void sendMessagesToListeners(List chatMessageList) { Map jsonCache = new HashMap<>(); for (ChatMessage message : chatMessageList) { diff --git a/backend/src/test/java/com/alttd/altitudeweb/controllers/chat/ChatControllerTest.java b/backend/src/test/java/com/alttd/altitudeweb/controllers/chat/ChatControllerTest.java new file mode 100644 index 0000000..8ff8986 --- /dev/null +++ b/backend/src/test/java/com/alttd/altitudeweb/controllers/chat/ChatControllerTest.java @@ -0,0 +1,34 @@ +package com.alttd.altitudeweb.controllers.chat; + +import com.alttd.altitudeweb.model.ServerStateDto; +import com.alttd.altitudeweb.services.chat.ChatService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ChatControllerTest { + + @Mock + private ChatService chatService; + + @InjectMocks + private ChatController chatController; + + @Test + void testUpdateServerStatesDelegatesToService() { + ServerStateDto serverStateDto = new ServerStateDto(); + + ResponseEntity response = chatController.updateServerStates(serverStateDto); + + verify(chatService).updateServerState(serverStateDto); + assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); + } +} diff --git a/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java b/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java index c5c47f2..4440916 100644 --- a/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java +++ b/backend/src/test/java/com/alttd/altitudeweb/services/chat/ChatServiceTest.java @@ -3,6 +3,8 @@ 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.model.ServerDto; +import com.alttd.altitudeweb.model.ServerStateDto; 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; @@ -18,6 +20,7 @@ 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.ArgumentMatchers.eq; import static org.mockito.Mockito.*; class ChatServiceTest { @@ -152,4 +155,28 @@ class ChatServiceTest { assertFalse(messageForUser.get(regularUser).contains("blocked message")); assertTrue(messageForUser.get(headModUser).contains("blocked message")); } + + @Test + void testUpdateServerStateFiltering() { + ServerStateDto serverStateDto = new ServerStateDto() + .addServersItem(new ServerDto().name("server1")) + .addServersItem(new ServerDto().name("server2")); + + chatService.updateServerState(serverStateDto); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageForUser.class); + verify(chatService).sendToUsers(eq("server-state"), captor.capture()); + + MessageForUser messageForUser = captor.getValue(); + + EventUser regularUser = new EventUser(UUID.randomUUID(), List.of(), null); + String regularResult = messageForUser.get(regularUser); + assertTrue(regularResult.contains("server1"), "Regular user should see allowed server"); + assertFalse(regularResult.contains("server2"), "Regular user should NOT see non-allowed server"); + + EventUser headModUser = new EventUser(UUID.randomUUID(), List.of(PermissionClaimDto.HEAD_MOD.getValue()), null); + String headModResult = messageForUser.get(headModUser); + assertTrue(headModResult.contains("server1"), "HEAD_MOD should see allowed server"); + assertTrue(headModResult.contains("server2"), "HEAD_MOD should see non-allowed server"); + } } diff --git a/open_api/src/main/resources/chat-api.yml b/open_api/src/main/resources/chat-api.yml index 8f77bc0..beefe33 100644 --- a/open_api/src/main/resources/chat-api.yml +++ b/open_api/src/main/resources/chat-api.yml @@ -53,6 +53,7 @@ components: - uuid - name - styledName + # TODO [Stijn] [2026-07-19]: Add who they ignore and who they are ignored by properties: uuid: @@ -140,11 +141,3 @@ components: type: array items: $ref: "#/components/schemas/Server" - parameters: - From: - name: from - in: path - required: true - schema: - type: string - format: date-time