diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatService.java b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatService.java new file mode 100644 index 0000000..29789b6 --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatService.java @@ -0,0 +1,32 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import java.time.Instant; + +import com.vaadin.flow.spring.security.AuthenticationContext; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Sinks; + +@Service +class ChatService { + + private final Sinks.Many messages = Sinks.many().multicast().directBestEffort(); + + private final Flux messagesFlux = messages.asFlux(); + + private final AuthenticationContext ctx; + + ChatService(AuthenticationContext ctx) { + this.ctx = ctx; + } + + Flux join() { + return this.messagesFlux; + } + + void add(String message) { + var username = this.ctx.getPrincipalName().orElse("Anonymous"); + this.messages.tryEmitNext(new Message(username, message, Instant.now())); + } + +} diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatView.java b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatView.java new file mode 100644 index 0000000..7be965f --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ChatView.java @@ -0,0 +1,34 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import jakarta.annotation.security.PermitAll; +import java.util.ArrayList; + +import com.vaadin.flow.component.messages.MessageInput; +import com.vaadin.flow.component.messages.MessageList; +import com.vaadin.flow.component.messages.MessageListItem; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.Command; + +@Route("") +@PermitAll +class ChatView extends VerticalLayout { + + ChatView(ChatService service) { + + var messageList = new MessageList(); + var textInput = new MessageInput(); + + setSizeFull(); + add(messageList, textInput); + expand(messageList); + textInput.setWidthFull(); + + service.join().subscribe(message -> { + var nl = new ArrayList<>(messageList.getItems()); + nl.add(new MessageListItem(message.text(), message.time(), message.username())); + getUI().ifPresent(ui -> ui.access((Command) () -> messageList.setItems(nl))); + }); + textInput.addSubmitListener(event -> service.add(event.getValue())); + } +} diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/LoginView.java b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/LoginView.java new file mode 100644 index 0000000..8384dc1 --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/LoginView.java @@ -0,0 +1,15 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import com.vaadin.flow.component.login.LoginForm; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; + +@Route("login") +class LoginView extends VerticalLayout { + + LoginView() { + var form = new LoginForm(); + form.setAction("login"); + add(form); + } +} diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/Message.java b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/Message.java new file mode 100644 index 0000000..33f8630 --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/Message.java @@ -0,0 +1,6 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import java.time.Instant; + +record Message(String username, String text, Instant time) { +} diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ReactiveVaadinChatApplication b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ReactiveVaadinChatApplication new file mode 100644 index 0000000..b7d51c1 --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/ReactiveVaadinChatApplication @@ -0,0 +1,19 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.component.page.Push; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; + +@SpringBootApplication +@Push +public class ReactiveVaadinChatApplication implements AppShellConfigurator { + public static void main(String[] args) { + SpringApplication springApplication = new SpringApplication(ReactiveVaadinChatApplication.class); + springApplication.setApplicationStartup(new BufferingApplicationStartup(2048)); + springApplication.run(args); + } +} + + diff --git a/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/SecurityConfiguration.java b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/SecurityConfiguration.java new file mode 100644 index 0000000..d99e2ba --- /dev/null +++ b/src/main/java/com/iqkv/incubator/sample/reactivevaadinchat/SecurityConfiguration.java @@ -0,0 +1,31 @@ +package com.iqkv.incubator.sample.reactivevaadinchat; + +import java.util.Set; + +import com.vaadin.flow.spring.security.VaadinWebSecurity; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.provisioning.UserDetailsManager; + +@Configuration +class SecurityConfiguration extends VaadinWebSecurity { + + @Override + protected void configure(HttpSecurity http) throws Exception { + super.configure(http); + setLoginView(http, LoginView.class); + } + + @Bean + UserDetailsManager userDetailsManager() { + var users = Set.of("tony", "steve", "bruce", "natasha") + .stream() + .map(name -> User.withDefaultPasswordEncoder().username(name).password("pwd").roles("USER").build()) + .toList(); + return new InMemoryUserDetailsManager(users); + } + +}