Implemented search users

This commit is contained in:
Andrey Terentev 2024-01-31 20:07:16 +07:00 committed by Andrey Terentev
parent 3e679f2b9a
commit be203ccf5c
11 changed files with 170 additions and 2 deletions

View File

@ -24,4 +24,6 @@ public interface UserRepository {
} }
List<RUser> all(RRoom room, Set<UserDetails> details) throws NodeNotFoundException, RoomNotFoundException; List<RUser> all(RRoom room, Set<UserDetails> details) throws NodeNotFoundException, RoomNotFoundException;
List<RUser> search(String input, Set<UserDetails> details);
} }

View File

@ -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<RUser> users) {}

View File

@ -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<UserDetails>();
for (var detailStr: detailsSeq.split(",")) {
try {
details.add(UserDetails.valueOf(detailStr.toUpperCase()));
} catch (IllegalArgumentException ignore) {}
}
return new SearchUserResponse(userService.searchUsers(input, details));
}
}

View File

@ -1,6 +1,7 @@
package ru.dragonestia.picker.repository; package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.RoomAreFullException; 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.Room;
import ru.dragonestia.picker.model.User; import ru.dragonestia.picker.model.User;
@ -19,4 +20,6 @@ public interface UserRepository {
void onRemoveRoom(Room room); void onRemoveRoom(Room room);
List<User> usersOf(Room room); List<User> usersOf(Room room);
List<User> search(String input);
} }

View File

@ -120,6 +120,16 @@ public class UserRepositoryImpl implements UserRepository {
} }
} }
@Override
public List<User> 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) { private record NodeRoomPath(String node, String bucket) {
@Override @Override

View File

@ -21,4 +21,6 @@ public interface UserService {
List<User> getRoomUsers(Room room); List<User> getRoomUsers(Room room);
List<RUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details); List<RUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details);
List<RUser> searchUsers(String input, Set<UserDetails> details);
} }

View File

@ -56,4 +56,20 @@ public class UserServiceImpl implements UserService {
} }
return users; return users;
} }
@Override
public List<RUser> searchUsers(String input, Set<UserDetails> 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();
}
} }

View File

@ -25,7 +25,7 @@ public class NamingValidator {
throw new InvalidRoomIdentifierException(nodeId, input); throw new InvalidRoomIdentifierException(nodeId, input);
} }
private boolean validateUserId(String input) { public boolean validateUserId(String input) {
return ValidateIdentifier.forUser(input); return ValidateIdentifier.forUser(input);
} }

View File

@ -30,7 +30,7 @@ public class MainLayout extends AppLayout {
private SideNav createSideNav() { private SideNav createSideNav() {
var nav = new SideNav(); var nav = new SideNav();
nav.addItem(new SideNavItem("Nodes list", NodesPage.class, VaadinIcon.FOLDER_O.create())); 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("Documentation", "https://github.com/ScarletRedMan/RoomPicker", VaadinIcon.BOOK.create()));
nav.addItem(new SideNavItem("Sign-out", HomePage.class, VaadinIcon.SIGN_OUT.create())); nav.addItem(new SideNavItem("Sign-out", HomePage.class, VaadinIcon.SIGN_OUT.create()));
return nav; return nav;

View File

@ -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<RUser> userGrid;
private List<RUser> 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<RUser> createUserGrid() {
var grid = new Grid<RUser>();
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));
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.http.HttpMethod;
import ru.dragonestia.picker.api.exception.NodeNotFoundException; import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException; 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.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser; import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.UserRepository; import ru.dragonestia.picker.api.repository.UserRepository;
@ -54,4 +55,17 @@ public class UserRepositoryImpl implements UserRepository {
params.put("requiredDetails", detailsStr); params.put("requiredDetails", detailsStr);
}).users(); }).users();
} }
@Override
public List<RUser> search(String input, Set<UserDetails> 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();
}
} }