From be203ccf5cc17e3ee753d6659bee7142ec4e43ab Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Wed, 31 Jan 2024 20:07:16 +0700 Subject: [PATCH] Implemented search users --- .../picker/api/repository/UserRepository.java | 2 + .../response/SearchUserResponse.java | 7 ++ .../picker/controller/UserController.java | 41 +++++++++++ .../picker/repository/UserRepository.java | 3 + .../repository/impl/UserRepositoryImpl.java | 10 +++ .../picker/service/UserService.java | 2 + .../picker/service/impl/UserServiceImpl.java | 16 ++++ .../picker/util/NamingValidator.java | 2 +- .../picker/cp/page/MainLayout.java | 2 +- .../picker/cp/page/UserSearchPage.java | 73 +++++++++++++++++++ .../repository/impl/UserRepositoryImpl.java | 14 ++++ 11 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/SearchUserResponse.java create mode 100644 app/src/main/java/ru/dragonestia/picker/controller/UserController.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/UserSearchPage.java diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java b/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java index 2781f21..28177d8 100644 --- a/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java @@ -24,4 +24,6 @@ default List all(RRoom room) throws NodeNotFoundException, RoomNotFoundEx } List all(RRoom room, Set details) throws NodeNotFoundException, RoomNotFoundException; + + List search(String input, Set details); } diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/SearchUserResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/SearchUserResponse.java new file mode 100644 index 0000000..d479fdd --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/SearchUserResponse.java @@ -0,0 +1,7 @@ +package ru.dragonestia.picker.api.repository.response; + +import ru.dragonestia.picker.api.repository.response.type.RUser; + +import java.util.List; + +public record SearchUserResponse(List users) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/UserController.java b/app/src/main/java/ru/dragonestia/picker/controller/UserController.java new file mode 100644 index 0000000..e903998 --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/controller/UserController.java @@ -0,0 +1,41 @@ +package ru.dragonestia.picker.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.dragonestia.picker.api.repository.details.UserDetails; +import ru.dragonestia.picker.api.repository.response.SearchUserResponse; +import ru.dragonestia.picker.service.UserService; +import ru.dragonestia.picker.util.NamingValidator; + +import java.util.HashSet; +import java.util.List; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/users") +public class UserController { + + private final UserService userService; + private final NamingValidator namingValidator; + + @GetMapping("/search") + SearchUserResponse search(@RequestParam(name = "input") String input, + @RequestParam(name = "requiredDetails", required = false, defaultValue = "") String detailsSeq) { + + if (!namingValidator.validateUserId(input) || input.isEmpty()) { + return new SearchUserResponse(List.of()); + } + + var details = new HashSet(); + for (var detailStr: detailsSeq.split(",")) { + try { + details.add(UserDetails.valueOf(detailStr.toUpperCase())); + } catch (IllegalArgumentException ignore) {} + } + + return new SearchUserResponse(userService.searchUsers(input, details)); + } +} diff --git a/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java index 96cc56b..5564f29 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java @@ -1,6 +1,7 @@ package ru.dragonestia.picker.repository; import ru.dragonestia.picker.api.exception.RoomAreFullException; +import ru.dragonestia.picker.api.repository.response.type.RUser; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; @@ -19,4 +20,6 @@ public interface UserRepository { void onRemoveRoom(Room room); List usersOf(Room room); + + List search(String input); } diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java index 96977fc..2966e8b 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java @@ -120,6 +120,16 @@ public List usersOf(Room room) { } } + @Override + public List search(String input) { + synchronized (usersMap) { + return usersMap.keySet().stream() + .filter(user -> user.id().startsWith(input)) + .sorted(Comparator.comparing(User::id)) + .toList(); + } + } + private record NodeRoomPath(String node, String bucket) { @Override diff --git a/app/src/main/java/ru/dragonestia/picker/service/UserService.java b/app/src/main/java/ru/dragonestia/picker/service/UserService.java index 41f6908..b3ef7af 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/UserService.java +++ b/app/src/main/java/ru/dragonestia/picker/service/UserService.java @@ -21,4 +21,6 @@ public interface UserService { List getRoomUsers(Room room); List getRoomUsersWithDetailsResponse(Room room, Set details); + + List searchUsers(String input, Set details); } diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java b/app/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java index 916c45d..d4a4b4c 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/UserServiceImpl.java @@ -56,4 +56,20 @@ public List getRoomUsersWithDetailsResponse(Room room, Set d } return users; } + + @Override + public List searchUsers(String input, Set details) { + return userRepository.search(input).stream() + .map(user -> { + var responseUser = user.toResponseObject(); + + for (var detail: details) { + if (detail == UserDetails.COUNT_ROOMS) { + responseUser.putDetail(UserDetails.COUNT_ROOMS, Integer.toString(getUserRooms(user).size())); + } + } + + return responseUser; + }).toList(); + } } diff --git a/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java b/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java index f51396b..947708c 100644 --- a/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java +++ b/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java @@ -25,7 +25,7 @@ public void validateRoomId(String nodeId, String input) throws InvalidRoomIdenti throw new InvalidRoomIdentifierException(nodeId, input); } - private boolean validateUserId(String input) { + public boolean validateUserId(String input) { return ValidateIdentifier.forUser(input); } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java index 6b10c3e..c590618 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/MainLayout.java @@ -30,7 +30,7 @@ private Component createLogo() { private SideNav createSideNav() { var nav = new SideNav(); nav.addItem(new SideNavItem("Nodes list", NodesPage.class, VaadinIcon.FOLDER_O.create())); - nav.addItem(new SideNavItem("Search users", HomePage.class, VaadinIcon.SEARCH.create())); + nav.addItem(new SideNavItem("Search users", UserSearchPage.class, VaadinIcon.SEARCH.create())); nav.addItem(new SideNavItem("Documentation", "https://github.com/ScarletRedMan/RoomPicker", VaadinIcon.BOOK.create())); nav.addItem(new SideNavItem("Sign-out", HomePage.class, VaadinIcon.SIGN_OUT.create())); return nav; diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/UserSearchPage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/UserSearchPage.java new file mode 100644 index 0000000..d4ecab6 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/UserSearchPage.java @@ -0,0 +1,73 @@ +package ru.dragonestia.picker.cp.page; + +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.grid.ColumnTextAlign; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import org.springframework.beans.factory.annotation.Autowired; +import ru.dragonestia.picker.api.repository.UserRepository; +import ru.dragonestia.picker.api.repository.details.UserDetails; +import ru.dragonestia.picker.api.repository.response.type.RUser; +import ru.dragonestia.picker.cp.component.Notifications; + +import java.util.LinkedList; +import java.util.List; + +@PageTitle("Search users") +@Route(value = "/users", layout = MainLayout.class) +public class UserSearchPage extends VerticalLayout { + + private final UserRepository userRepository; + private final TextField fieldUsername; + private final Grid userGrid; + private List cachedUsers = new LinkedList<>(); + + @Autowired + public UserSearchPage(UserRepository userRepository) { + this.userRepository = userRepository; + + add(fieldUsername = createUsernameInputField()); + add(userGrid = createUserGrid()); + } + + private TextField createUsernameInputField() { + var field = new TextField(); + field.setLabel("Username"); + field.setPlaceholder("some-user-identifier"); + field.setRequired(true); + field.setMinWidth(30, Unit.PERCENTAGE); + + var button = new Button(new Icon(VaadinIcon.SEARCH)); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + button.getStyle().set("color", "#FFFFFF"); + button.addClickListener(event -> search(fieldUsername.getValue().trim())); + + field.setSuffixComponent(button); + return field; + } + + private Grid createUserGrid() { + var grid = new Grid(); + + grid.addColumn(RUser::getId).setHeader("Identifier") + .setFooter("Found %s users".formatted(cachedUsers.size())); + + grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms"); + + grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO + + return grid; + } + + private void search(String input) { + userGrid.setItems(cachedUsers = userRepository.search(input, UserRepository.ALL_DETAILS)); + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java index 33c5931..a3e95f9 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java @@ -7,6 +7,7 @@ import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.api.exception.RoomNotFoundException; +import ru.dragonestia.picker.api.repository.response.SearchUserResponse; import ru.dragonestia.picker.api.repository.response.type.RRoom; import ru.dragonestia.picker.api.repository.response.type.RUser; import ru.dragonestia.picker.api.repository.UserRepository; @@ -54,4 +55,17 @@ public List all(RRoom room, Set details) throws NodeNotFound params.put("requiredDetails", detailsStr); }).users(); } + + @Override + public List search(String input, Set details) { + return rest.query("/users/search", + HttpMethod.GET, + SearchUserResponse.class, + params -> { + var detailsStr = String.join(",", details.stream().map(Enum::toString).toList()); + + params.put("requiredDetails", detailsStr); + params.put("input", input); + }).users(); + } }