diff --git a/src/main/java/dev/struchkov/example/ChatMessageDecoder.java b/src/main/java/dev/struchkov/example/ChatMessageDecoder.java deleted file mode 100644 index 7c6ab67..0000000 --- a/src/main/java/dev/struchkov/example/ChatMessageDecoder.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.struchkov.example; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import jakarta.websocket.DecodeException; -import jakarta.websocket.Decoder; -import lombok.SneakyThrows; - -public class ChatMessageDecoder implements Decoder.Text { - - private final ObjectMapper jackson = ChatMessageDecoder.getJackson(); - - @Override - @SneakyThrows - public ChatInputMessage decode(String s) throws DecodeException { - return jackson.readValue(s, ChatInputMessage.class); - } - - @Override - public boolean willDecode(String s) { - return s != null; - } - - public static ObjectMapper getJackson() { - ObjectMapper om = new ObjectMapper(); - om.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); - om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - om.registerModule(new JavaTimeModule()); - return om; - } - -} diff --git a/src/main/java/dev/struchkov/example/ChatMessageEncoder.java b/src/main/java/dev/struchkov/example/ChatMessageEncoder.java deleted file mode 100644 index 2798f58..0000000 --- a/src/main/java/dev/struchkov/example/ChatMessageEncoder.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.struchkov.example; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.websocket.EncodeException; -import jakarta.websocket.Encoder; -import lombok.SneakyThrows; - -public class ChatMessageEncoder implements Encoder.Text { - - private final ObjectMapper jackson = ChatMessageDecoder.getJackson(); - - @Override - @SneakyThrows - public String encode(ChatOutputMessage chatOutputMessage) throws EncodeException { - return jackson.writeValueAsString(chatOutputMessage); - } - -} diff --git a/src/main/java/dev/struchkov/example/GreetingConfig.java b/src/main/java/dev/struchkov/example/GreetingConfig.java deleted file mode 100644 index b67c55a..0000000 --- a/src/main/java/dev/struchkov/example/GreetingConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.struchkov.example; - -import io.smallrye.config.ConfigMapping; -import io.smallrye.config.WithName; - -@ConfigMapping(prefix = "greeting") -public interface GreetingConfig { - - @WithName("message") - String message(); - -} \ No newline at end of file diff --git a/src/main/java/dev/struchkov/example/HelloGraphQLResource.java b/src/main/java/dev/struchkov/example/HelloGraphQLResource.java deleted file mode 100644 index 618e496..0000000 --- a/src/main/java/dev/struchkov/example/HelloGraphQLResource.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.struchkov.example; - -import org.eclipse.microprofile.graphql.DefaultValue; -import org.eclipse.microprofile.graphql.Description; -import org.eclipse.microprofile.graphql.GraphQLApi; -import org.eclipse.microprofile.graphql.Query; - -@GraphQLApi -public class HelloGraphQLResource { - - @Query - @Description("Say hello") - public String sayHello(@DefaultValue("World") String name) { - return "Hello " + name; - } - -} \ No newline at end of file diff --git a/src/main/java/dev/struchkov/example/StartWebSocket.java b/src/main/java/dev/struchkov/example/WebSocket.java similarity index 57% rename from src/main/java/dev/struchkov/example/StartWebSocket.java rename to src/main/java/dev/struchkov/example/WebSocket.java index 7c2234c..2b3590c 100644 --- a/src/main/java/dev/struchkov/example/StartWebSocket.java +++ b/src/main/java/dev/struchkov/example/WebSocket.java @@ -1,5 +1,12 @@ package dev.struchkov.example; +import dev.struchkov.example.convert.EventContainerDecoder; +import dev.struchkov.example.convert.EventContainerEncoder; +import dev.struchkov.example.domain.EventContainer; +import dev.struchkov.example.domain.input.ChatInputMessage; +import dev.struchkov.example.domain.input.ChatViewInput; +import dev.struchkov.example.domain.output.ChatOutputMessage; +import dev.struchkov.example.domain.output.ChatViewOutput; import jakarta.enterprise.context.ApplicationScoped; import jakarta.websocket.OnClose; import jakarta.websocket.OnError; @@ -10,6 +17,7 @@ import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import lombok.RequiredArgsConstructor; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -20,11 +28,11 @@ import java.util.concurrent.ConcurrentHashMap; @ApplicationScoped @ServerEndpoint( value = "/chat/{chatId}", - decoders = ChatMessageDecoder.class, - encoders = ChatMessageEncoder.class + decoders = EventContainerDecoder.class, + encoders = EventContainerEncoder.class ) @RequiredArgsConstructor -public class StartWebSocket { +public class WebSocket { public static final ThreadLocal CURRENT_USER = new ThreadLocal<>(); private final Map> sessions = new ConcurrentHashMap<>(); @@ -47,9 +55,30 @@ public class StartWebSocket { } @OnMessage - public void onMessage(Session session, @PathParam("chatId") String chatId, ChatInputMessage message) { - System.out.println("onMessage> " + chatId + ": " + message); - sendMessage(session, chatId, message); + public void onMessage(Session session, @PathParam("chatId") String chatId, EventContainer event) { + System.out.println("onMessage> " + chatId + ": " + event); + switch (event.getEventType()) { + case MESSAGE_NEW -> sendMessage(session, chatId, (ChatInputMessage) event.getEvent()); + case MESSAGE_VIEWED -> viewMessage(session, chatId, (ChatViewInput) event.getEvent()); + } + } + + private void viewMessage(Session session, String chatId, ChatViewInput viewInput) { + final List chatSessions = sessions.get(chatId); + for (Session chatSession : chatSessions) { + if (session.getId().equals(chatSession.getId())) { + continue; + } + final UUID fromUserId = CURRENT_USER.get(); + final ChatViewOutput chatViewOutput = new ChatViewOutput( + viewInput.getMessageId(), + fromUserId, + LocalDateTime.now() + ); + final EventContainer eventContainer = EventContainer.viewedOutput(chatViewOutput); + chatSession.getAsyncRemote().sendObject(eventContainer); + CURRENT_USER.remove(); + } } private void sendMessage(Session session, String chatId, ChatInputMessage message) { @@ -60,7 +89,8 @@ public class StartWebSocket { } final UUID fromUserId = CURRENT_USER.get(); final ChatOutputMessage outputMessage = new ChatOutputMessage(fromUserId, message.getText()); - chatSession.getAsyncRemote().sendObject(outputMessage); + final EventContainer eventContainer = EventContainer.messageOutput(outputMessage); + chatSession.getAsyncRemote().sendObject(eventContainer); CURRENT_USER.remove(); } } diff --git a/src/main/java/dev/struchkov/example/convert/EventContainerDecoder.java b/src/main/java/dev/struchkov/example/convert/EventContainerDecoder.java new file mode 100644 index 0000000..85ffd9d --- /dev/null +++ b/src/main/java/dev/struchkov/example/convert/EventContainerDecoder.java @@ -0,0 +1,44 @@ +package dev.struchkov.example.convert; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import dev.struchkov.example.domain.EventContainer; +import dev.struchkov.example.domain.EventType; +import dev.struchkov.example.domain.input.ChatInputMessage; +import dev.struchkov.example.domain.input.ChatViewInput; +import jakarta.websocket.DecodeException; +import jakarta.websocket.Decoder; +import lombok.SneakyThrows; + +public class EventContainerDecoder implements Decoder.Text { + + private final ObjectMapper jackson = EventContainerDecoder.getJackson(); + + @Override + @SneakyThrows + public EventContainer decode(String s) throws DecodeException { + final String eventType = jackson.readTree(s).get("eventType").asText(); + final JsonNode event = jackson.readTree(s).get("event"); + return switch (EventType.valueOf(eventType)) { + case MESSAGE_NEW -> EventContainer.messageInput(jackson.treeToValue(event, ChatInputMessage.class)); + case MESSAGE_VIEWED -> EventContainer.viewInput(jackson.treeToValue(event, ChatViewInput.class)); + }; + } + + @Override + public boolean willDecode(String s) { + return s != null; + } + + public static ObjectMapper getJackson() { + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + om.registerModule(new JavaTimeModule()); + return om; + } + +} diff --git a/src/main/java/dev/struchkov/example/convert/EventContainerEncoder.java b/src/main/java/dev/struchkov/example/convert/EventContainerEncoder.java new file mode 100644 index 0000000..7416fb4 --- /dev/null +++ b/src/main/java/dev/struchkov/example/convert/EventContainerEncoder.java @@ -0,0 +1,19 @@ +package dev.struchkov.example.convert; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.struchkov.example.domain.EventContainer; +import jakarta.websocket.EncodeException; +import jakarta.websocket.Encoder; +import lombok.SneakyThrows; + +public class EventContainerEncoder implements Encoder.Text { + + private final ObjectMapper jackson = EventContainerDecoder.getJackson(); + + @Override + @SneakyThrows + public String encode(EventContainer eventContainer) throws EncodeException { + return jackson.writeValueAsString(eventContainer); + } + +} diff --git a/src/main/java/dev/struchkov/example/domain/EventContainer.java b/src/main/java/dev/struchkov/example/domain/EventContainer.java new file mode 100644 index 0000000..1810f15 --- /dev/null +++ b/src/main/java/dev/struchkov/example/domain/EventContainer.java @@ -0,0 +1,54 @@ +package dev.struchkov.example.domain; + +import dev.struchkov.example.domain.input.ChatInputMessage; +import dev.struchkov.example.domain.input.ChatViewInput; +import dev.struchkov.example.domain.output.ChatOutputMessage; +import dev.struchkov.example.domain.output.ChatViewOutput; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Builder +@ToString +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class EventContainer { + + private EventType eventType; + private Object event; + + public static EventContainer messageInput(ChatInputMessage chatInputMessage) { + return EventContainer.builder() + .eventType(EventType.MESSAGE_NEW) + .event(chatInputMessage) + .build(); + } + + public static EventContainer viewInput(ChatViewInput chatViewInput) { + return EventContainer.builder() + .eventType(EventType.MESSAGE_VIEWED) + .event(chatViewInput) + .build(); + } + + public static EventContainer viewedOutput(ChatViewOutput chatViewOutput) { + return EventContainer.builder() + .eventType(EventType.MESSAGE_VIEWED) + .event(chatViewOutput) + .build(); + } + + public static EventContainer messageOutput(ChatOutputMessage chatOutputMessage) { + return EventContainer.builder() + .eventType(EventType.MESSAGE_NEW) + .event(chatOutputMessage) + .build(); + } + +} diff --git a/src/main/java/dev/struchkov/example/domain/EventType.java b/src/main/java/dev/struchkov/example/domain/EventType.java new file mode 100644 index 0000000..9e67e67 --- /dev/null +++ b/src/main/java/dev/struchkov/example/domain/EventType.java @@ -0,0 +1,7 @@ +package dev.struchkov.example.domain; + +public enum EventType { + + MESSAGE_NEW, MESSAGE_VIEWED + +} diff --git a/src/main/java/dev/struchkov/example/ChatInputMessage.java b/src/main/java/dev/struchkov/example/domain/input/ChatInputMessage.java similarity index 78% rename from src/main/java/dev/struchkov/example/ChatInputMessage.java rename to src/main/java/dev/struchkov/example/domain/input/ChatInputMessage.java index 1ebbe10..0a8e1ff 100644 --- a/src/main/java/dev/struchkov/example/ChatInputMessage.java +++ b/src/main/java/dev/struchkov/example/domain/input/ChatInputMessage.java @@ -1,4 +1,4 @@ -package dev.struchkov.example; +package dev.struchkov.example.domain.input; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/dev/struchkov/example/domain/input/ChatViewInput.java b/src/main/java/dev/struchkov/example/domain/input/ChatViewInput.java new file mode 100644 index 0000000..558f8a8 --- /dev/null +++ b/src/main/java/dev/struchkov/example/domain/input/ChatViewInput.java @@ -0,0 +1,14 @@ +package dev.struchkov.example.domain.input; + +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +public class ChatViewInput { + + private UUID messageId; + +} diff --git a/src/main/java/dev/struchkov/example/ChatOutputMessage.java b/src/main/java/dev/struchkov/example/domain/output/ChatOutputMessage.java similarity index 85% rename from src/main/java/dev/struchkov/example/ChatOutputMessage.java rename to src/main/java/dev/struchkov/example/domain/output/ChatOutputMessage.java index 0c3867c..7e8f19b 100644 --- a/src/main/java/dev/struchkov/example/ChatOutputMessage.java +++ b/src/main/java/dev/struchkov/example/domain/output/ChatOutputMessage.java @@ -1,4 +1,4 @@ -package dev.struchkov.example; +package dev.struchkov.example.domain.output; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/dev/struchkov/example/domain/output/ChatViewOutput.java b/src/main/java/dev/struchkov/example/domain/output/ChatViewOutput.java new file mode 100644 index 0000000..04c8b38 --- /dev/null +++ b/src/main/java/dev/struchkov/example/domain/output/ChatViewOutput.java @@ -0,0 +1,21 @@ +package dev.struchkov.example.domain.output; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Getter +@Setter +@ToString +@AllArgsConstructor +public class ChatViewOutput { + + private UUID messageId; + private UUID chatMemberId; + private LocalDateTime dateRead; + +} diff --git a/src/main/java/dev/struchkov/example/WebsocketAuthFilter.java b/src/main/java/dev/struchkov/example/filter/WebsocketAuthFilter.java similarity index 84% rename from src/main/java/dev/struchkov/example/WebsocketAuthFilter.java rename to src/main/java/dev/struchkov/example/filter/WebsocketAuthFilter.java index 97c03be..2be7776 100644 --- a/src/main/java/dev/struchkov/example/WebsocketAuthFilter.java +++ b/src/main/java/dev/struchkov/example/filter/WebsocketAuthFilter.java @@ -1,5 +1,6 @@ -package dev.struchkov.example; +package dev.struchkov.example.filter; +import dev.struchkov.example.WebSocket; import io.quarkus.vertx.web.RouteFilter; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpServerRequest; @@ -39,10 +40,10 @@ public class WebsocketAuthFilter { private boolean authLogic(String sessionId) { // your auth logic here if (sessionId.equals("user1")) { - StartWebSocket.CURRENT_USER.set(UUID.fromString("09e429de-a302-40b6-9d10-6b113ab9e89d")); + WebSocket.CURRENT_USER.set(UUID.fromString("09e429de-a302-40b6-9d10-6b113ab9e89d")); return true; } else if (sessionId.equals("user2")) { - StartWebSocket.CURRENT_USER.set(UUID.fromString("f84dbae1-f9a9-4c37-8922-4eb207103676")); + WebSocket.CURRENT_USER.set(UUID.fromString("f84dbae1-f9a9-4c37-8922-4eb207103676")); return true; } else { return false;