Refactored control panel

This commit is contained in:
Andrey Terentev 2024-03-13 13:30:49 +07:00 committed by Andrey Terentev
parent a29471f2c5
commit 0ba5ff8045
26 changed files with 335 additions and 563 deletions

View File

@ -2,6 +2,7 @@ package ru.dragonestia.picker.api.model.room;
import io.swagger.v3.oas.annotations.media.Schema;
import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.picker.api.repository.type.RoomPath;
@ -64,11 +65,23 @@ public class ResponseRoom implements IRoom {
return slots;
}
@Contract("_ -> this")
public @NotNull ResponseRoom setSlots(int slots) {
this.slots = slots;
return this;
}
@Override
public boolean isLocked() {
return locked;
}
@Contract("_ -> this")
public @NotNull ResponseRoom setLocked(boolean locked) {
this.locked = locked;
return this;
}
@Override
public @Nullable Boolean isPersist() {
var val = getDetail(RoomDetails.PERSIST);
@ -80,6 +93,12 @@ public class ResponseRoom implements IRoom {
return payload;
}
@Contract("_ -> this")
public @NotNull ResponseRoom setPayload(@NotNull String payload) {
this.payload = payload;
return this;
}
@Transient
@Override
public boolean hasUnlimitedSlots() {

View File

@ -5,8 +5,10 @@ import org.jetbrains.annotations.NotNull;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.repository.type.UserIdentifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class FindRoomsLinkedWithUser {
@ -26,6 +28,21 @@ public class FindRoomsLinkedWithUser {
return details;
}
@Contract("_ -> new")
public static @NotNull FindRoomsLinkedWithUser just(@NotNull UserIdentifier identifier) {
return FindRoomsLinkedWithUser.builder()
.setUserId(identifier)
.build();
}
@Contract("_ -> new")
public static @NotNull FindRoomsLinkedWithUser withAllDetails(@NotNull UserIdentifier identifier) {
return FindRoomsLinkedWithUser.builder()
.setUserId(identifier)
.setDetails(Arrays.stream(RoomDetails.values()).collect(Collectors.toSet()))
.build();
}
public static @NotNull Builder builder() {
return new Builder();
}

View File

@ -6,9 +6,11 @@ import ru.dragonestia.picker.api.model.user.UserDetails;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class GetAllUsersFromRoom {
@ -34,6 +36,21 @@ public class GetAllUsersFromRoom {
return details;
}
public static @NotNull GetAllUsersFromRoom just(@NotNull NodeIdentifier nodeId, @NotNull RoomIdentifier roomId) {
return GetAllUsersFromRoom.builder()
.setNodeId(nodeId)
.setRoomId(roomId)
.build();
}
public static @NotNull GetAllUsersFromRoom withAllDetails(@NotNull NodeIdentifier nodeId, @NotNull RoomIdentifier roomId) {
return GetAllUsersFromRoom.builder()
.setNodeId(nodeId)
.setRoomId(roomId)
.setDetails(Arrays.stream(UserDetails.values()).collect(Collectors.toSet()))
.build();
}
public static @NotNull Builder builder() {
return new Builder();
}

View File

@ -28,6 +28,7 @@ vaadin {
dependencies {
implementation project(":client-api")
implementation project(":client-impl")
implementation 'com.vaadin:vaadin-spring-boot-starter'
compileOnly 'org.projectlombok:lombok'

View File

@ -0,0 +1,13 @@
package ru.dragonestia.picker.cp.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface ServerURL {}

View File

@ -12,8 +12,10 @@ import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import lombok.Getter;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.model.user.UserDefinition;
import ru.dragonestia.picker.api.repository.type.UserIdentifier;
import java.util.Collection;
import java.util.List;
@ -22,16 +24,14 @@ import java.util.function.BiConsumer;
public class AddUsers extends Details {
private final RRoom room;
private final BiConsumer<Collection<RUser>, Boolean> onCommit;
private final BiConsumer<Collection<IUser>, Boolean> onCommit;
private final Checkbox ignoreSlots;
private final VerticalLayout usersLayout;
private final AtomicInteger freeUserIdNumber = new AtomicInteger(1);
public AddUsers(RRoom room, BiConsumer<Collection<RUser>, Boolean> onCommit) {
public AddUsers(IRoom room, BiConsumer<Collection<IUser>, Boolean> onCommit) {
super(new H2("Add users"));
this.room = room;
this.onCommit = onCommit;
usersLayout = new VerticalLayout();
@ -49,14 +49,14 @@ public class AddUsers extends Details {
usersLayout.add(new UserEntry(false, freeUserIdNumber.getAndIncrement()));
}
public List<RUser> readAllUsers() {
public List<IUser> readAllUsers() {
return usersLayout.getChildren()
.filter(component -> component instanceof UserEntry)
.map(component -> (UserEntry) component)
.map(user -> user.getUserIdentifierField().getValue())
.map(String::trim)
.filter(user -> !user.isEmpty())
.map(RUser::new)
.map(id -> (IUser) new UserDefinition(UserIdentifier.of(id)))
.toList();
}

View File

@ -14,22 +14,20 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import lombok.Setter;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.node.NodeDetails;
import ru.dragonestia.picker.api.repository.NodeRepository;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.request.node.GetAllNodes;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
public class NodeList extends VerticalLayout implements RefreshableTable {
private final NodeRepository nodeRepository;
private final Grid<RNode> nodesGrid;
private final Grid<INode> nodesGrid;
private final TextField searchField;
private List<RNode> cachedNodes;
@Setter private Consumer<String> removeMethod;
private List<INode> cachedNodes;
public NodeList(NodeRepository nodeRepository) {
super();
@ -56,24 +54,24 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
var temp = input.trim();
nodesGrid.setItems(cachedNodes.stream()
.filter(node -> node.getId().startsWith(temp))
.filter(node -> node.getIdentifier().startsWith(temp))
.toList());
}
private Grid<RNode> createGrid() {
var grid = new Grid<>(RNode.class, false);
private Grid<INode> createGrid() {
var grid = new Grid<>(INode.class, false);
grid.addComponentColumn(node -> {
if (Boolean.parseBoolean(node.getDetails(NodeDetails.PERSIST))) {
return new Span(node.getId());
if (Boolean.parseBoolean(node.getDetail(NodeDetails.PERSIST))) {
return new Span(node.getIdentifier());
}
var result = new Span(node.getId());
var result = new Span(node.getIdentifier());
result.add(grayBadge("(temp)"));
return result;
}).setHeader("Identifier").setComparator(Comparator.comparing(RNode::getId)).setSortable(true);
}).setHeader("Identifier").setComparator(Comparator.comparing(INode::getIdentifier)).setSortable(true);
grid.addColumn(node -> node.getMode().getName()).setHeader("Mode").setSortable(true);
grid.addColumn(node -> node.getPickingMethod().name()).setHeader("Mode").setSortable(true);
grid.addComponentColumn(this::createManageButtons).setFrozenToEnd(true)
.setTextAlign(ColumnTextAlign.END).setHeader(createRefreshButton());
@ -82,7 +80,7 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
return grid;
}
private HorizontalLayout createManageButtons(RNode node) {
private HorizontalLayout createManageButtons(INode node) {
var layout = new HorizontalLayout(JustifyContentMode.END);
{
@ -102,13 +100,13 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
return layout;
}
private void clickDetailsButton(RNode node) {
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.getId()));
private void clickDetailsButton(INode node) {
getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.getIdentifier()));
}
private void clickRemoveButton(RNode node) {
private void clickRemoveButton(INode node) {
var dialog = new Dialog("Confirm node deletion");
dialog.add(new Html("<p>Confirm that you want to delete node. Enter <b><u>" + node.getId() + "</u></b> to field below and confirm.</p>"));
dialog.add(new Html("<p>Confirm that you want to delete node. Enter <b><u>" + node.getIdentifier() + "</u></b> to field below and confirm.</p>"));
var inputField = new TextField();
inputField.setWidth("100%");
@ -118,13 +116,13 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
var button = new Button("Confirm");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
button.addClickListener(event -> {
if (!node.getId().equals(inputField.getValue())) {
if (!node.getIdentifier().equals(inputField.getValue())) {
Notifications.error("Invalid input");
return;
}
removeNode(node);
Notifications.success("Node <b>" + node.getId() + "</b> was successfully removed!");
Notifications.success("Node <b>" + node.getIdentifier() + "</b> was successfully removed!");
dialog.close();
});
@ -140,15 +138,14 @@ public class NodeList extends VerticalLayout implements RefreshableTable {
dialog.open();
}
private void removeNode(RNode node) {
if (removeMethod != null) {
removeMethod.accept(node.getId());
}
private void removeNode(INode node) {
nodeRepository.removeNode(node);
refresh();
}
@Override
public void refresh() {
cachedNodes = nodeRepository.all(NodeRepository.ALL_DETAILS);
cachedNodes = nodeRepository.allNodes(GetAllNodes.WITH_ALL_DETAILS);
applySearch(searchField.getValue());
}

View File

@ -15,19 +15,20 @@ import com.vaadin.flow.component.textfield.Autocomplete;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import org.springframework.lang.Nullable;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.type.PickingMode;
import ru.dragonestia.picker.api.model.node.NodeDefinition;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import java.util.function.BiFunction;
import java.util.function.Function;
public class RegisterNode extends Details {
private final BiFunction<RNode, Boolean, Response> onSubmit;
private final Function<NodeDefinition, Response> onSubmit;
private final TextField identifierField;
private final RadioButtonGroup<PickingMode> modeRadio;
private final RadioButtonGroup<PickingMethod> modeRadio;
private final Checkbox persistField;
public RegisterNode(BiFunction<RNode, Boolean, Response> onSubmit) {
public RegisterNode(Function<NodeDefinition, Response> onSubmit) {
super(new H2("Register node"));
this.onSubmit = onSubmit;
@ -63,15 +64,15 @@ public class RegisterNode extends Details {
return button;
}
private RadioButtonGroup<PickingMode> createModeRadio() {
var radio = new RadioButtonGroup<PickingMode>("Mode");
private RadioButtonGroup<PickingMethod> createModeRadio() {
var radio = new RadioButtonGroup<PickingMethod>("Mode");
radio.addThemeVariants(RadioGroupVariant.LUMO_VERTICAL);
radio.setRenderer(new ComponentRenderer<Component, PickingMode>(mode -> new Span(mode.getName())));
radio.setItems(PickingMode.SEQUENTIAL_FILLING,
PickingMode.ROUND_ROBIN,
PickingMode.LEAST_PICKED);
radio.setRenderer(new ComponentRenderer<Component, PickingMethod>(mode -> new Span(mode.name())));
radio.setItems(PickingMethod.SEQUENTIAL_FILLING,
PickingMethod.ROUND_ROBIN,
PickingMethod.LEAST_PICKED);
radio.setValue(PickingMode.SEQUENTIAL_FILLING);
radio.setValue(PickingMethod.SEQUENTIAL_FILLING);
return radio;
}
@ -101,8 +102,10 @@ public class RegisterNode extends Details {
return;
}
var node = new RNode(nodeIdentifier, modeRadio.getValue());
var response = onSubmit.apply(node, persistField.getValue());
var node = new NodeDefinition(NodeIdentifier.of(nodeIdentifier))
.setPickingMethod(modeRadio.getValue())
.setPersist(persistField.getValue());
var response = onSubmit.apply(node);
clear();
if (response.error()) {
Notifications.error(response.reason());

View File

@ -11,22 +11,23 @@ import com.vaadin.flow.component.textfield.Autocomplete;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import org.springframework.lang.Nullable;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.room.RoomDefinition;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import java.util.function.BiFunction;
import java.util.function.Function;
public class RegisterRoom extends Details {
private final RNode node;
private final BiFunction<RRoom, Boolean, Response> onSubmit;
private final INode node;
private final Function<RoomDefinition, Response> onSubmit;
private final TextField identifierField;
private final TextArea payloadField;
private final Checkbox lockedField;
private final Checkbox persistField;
public RegisterRoom(RNode node, BiFunction<RRoom, Boolean, Response> onSubmit) {
public RegisterRoom(INode node, Function<RoomDefinition, Response> onSubmit) {
super(new H2("Register room"));
this.node = node;
this.onSubmit = onSubmit;
@ -45,7 +46,7 @@ public class RegisterRoom extends Details {
private TextField createNodeIdentifierField() {
var field = new TextField("Node identifier");
field.setMinWidth(20, Unit.REM);
field.setValue(node.getId());
field.setValue(node.getIdentifier());
field.setReadOnly(true);
return field;
}
@ -103,10 +104,10 @@ public class RegisterRoom extends Details {
}
private void onClick() {
var nodeIdentifier = identifierField.getValue();
var roomId = identifierField.getValue();
String error = null;
if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) {
if (identifierField.isInvalid() || (error = validateForm(roomId)) != null) {
if (identifierField.isInvalid()) {
error = "Invalid room id format";
}
@ -115,9 +116,13 @@ public class RegisterRoom extends Details {
return;
}
var room = new RRoom(nodeIdentifier, node, RRoom.INFINITE_SLOTS, payloadField.getValue());
var room = new RoomDefinition(node.getIdentifierObject(), RoomIdentifier.of(roomId))
.setMaxSlots(IRoom.UNLIMITED_SLOTS)
.setPayload(payloadField.getValue())
.setPersist(persistField.getValue());
room.setLocked(lockedField.getValue());
var response = onSubmit.apply(room, persistField.getValue());
var response = onSubmit.apply(room);
clear();
if (response.error()) {
Notifications.error(response.reason());

View File

@ -14,28 +14,26 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.request.room.GetAllRooms;
import java.util.List;
import java.util.function.Consumer;
@Log4j2
public class RoomList extends VerticalLayout implements RefreshableTable {
private final RNode node;
private final INode node;
private final RoomRepository roomRepository;
private final Grid<RRoom.Short> roomsGrid;
private final Grid<ShortResponseRoom> roomsGrid;
private final TextField searchField;
private List<RRoom.Short> cachedRooms;
private List<ShortResponseRoom> cachedRooms;
private final Span totalUsers = new Span();
@Setter private Consumer<RRoom.Short> removeMethod;
public RoomList(RNode node, RoomRepository roomRepository) {
public RoomList(INode node, RoomRepository roomRepository) {
this.node = node;
this.roomRepository = roomRepository;
@ -60,27 +58,27 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
var temp = input.trim();
roomsGrid.setItems(cachedRooms.stream()
.filter(room -> room.id().startsWith(temp))
.filter(room -> room.getIdentifier().startsWith(temp))
.toList());
}
private Grid<RRoom.Short> createGrid() {
var grid = new Grid<>(RRoom.Short.class, false);
private Grid<ShortResponseRoom> createGrid() {
var grid = new Grid<>(ShortResponseRoom.class, false);
grid.addColumn(RRoom.Short::id).setHeader("Identifier").setSortable(true);
grid.addColumn(ShortResponseRoom::getIdentifier).setHeader("Identifier").setSortable(true);
grid.addComponentColumn(room -> {
var result = new Span();
if (room.slots() == -1) {
if (room.getMaxSlots() == -1) {
result.setText("Unlimited");
result.getElement().getThemeList().add("badge contrast");
} else {
result.setText(Integer.toString(room.slots()));
result.setText(Integer.toString(room.getMaxSlots()));
}
return result;
}).setHeader("Slots").setComparator((room1, room2) -> {
var r1 = room1.slots() == -1? Integer.MAX_VALUE : room1.slots();
var r2 = room2.slots() == -1? Integer.MAX_VALUE : room2.slots();
var r1 = room1.hasUnlimitedSlots()? Integer.MAX_VALUE : room1.getMaxSlots();
var r2 = room2.hasUnlimitedSlots()? Integer.MAX_VALUE : room2.getMaxSlots();
return Integer.compare(r1, r2);
}).setSortable(true).setTextAlign(ColumnTextAlign.CENTER);
@ -89,24 +87,24 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
.setComparator((room1, room2) -> Integer.compare(getUsers(room1), getUsers(room2))).setSortable(true)
.setTextAlign(ColumnTextAlign.CENTER).setFooter(totalUsers);
grid.addColumn(room -> Math.max(UserList.getUsingPercentage(room.slots(), getUsers(room)), 0) + "%")
grid.addColumn(room -> Math.max(UserList.getUsingPercentage(room.getMaxSlots(), getUsers(room)), 0) + "%")
.setComparator((room1, room2) -> {
var p1 = UserList.getUsingPercentage(room1.slots(), getUsers(room1));
var p2 = UserList.getUsingPercentage(room2.slots(), getUsers(room2));
var p1 = UserList.getUsingPercentage(room1.getMaxSlots(), getUsers(room1));
var p2 = UserList.getUsingPercentage(room2.getMaxSlots(), getUsers(room2));
return Integer.compare(p1, p2);
}).setHeader("Occupancy").setTextAlign(ColumnTextAlign.CENTER);
grid.addComponentColumn(room -> {
var result = new Span();
if (room.locked()) {
if (room.isLocked()) {
result.setText("Yes");
result.getElement().getThemeList().add("badge error");
} else {
result.setText("No");
}
return result;
}).setComparator((room1, room2) -> Boolean.compare(room1.locked(), room2.locked())).setSortable(true)
}).setComparator((room1, room2) -> Boolean.compare(room1.isLocked(), room2.isLocked())).setSortable(true)
.setHeader("Locked").setTextAlign(ColumnTextAlign.CENTER);
grid.addComponentColumn(this::createManageButtons).setFrozenToEnd(true)
@ -116,7 +114,7 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
return grid;
}
private HorizontalLayout createManageButtons(RRoom.Short room) {
private HorizontalLayout createManageButtons(ShortResponseRoom room) {
var layout = new HorizontalLayout(JustifyContentMode.END);
{
@ -136,15 +134,15 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
return layout;
}
private void clickDetailsButton(RRoom.Short room) {
private void clickDetailsButton(ShortResponseRoom room) {
getUI().ifPresent(ui -> {
ui.navigate("/nodes/%s/rooms/%s".formatted(node.getId(), room.id()));
ui.navigate("/nodes/%s/rooms/%s".formatted(node.getIdentifier(), room.getIdentifier()));
});
}
private void clickRemoveButton(RRoom.Short room) {
private void clickRemoveButton(ShortResponseRoom room) {
var dialog = new Dialog("Confirm room deletion");
dialog.add(new Html("<p>Confirm that you want to delete room. Enter <b><u>" + room.id() + "</u></b> to field below and confirm.</p>"));
dialog.add(new Html("<p>Confirm that you want to delete room. Enter <b><u>" + room.getIdentifier() + "</u></b> to field below and confirm.</p>"));
var inputField = new TextField();
inputField.setWidth("100%");
@ -154,13 +152,13 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
var button = new Button("Confirm");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
button.addClickListener(event -> {
if (!room.id().equals(inputField.getValue())) {
if (!room.getIdentifier().equals(inputField.getValue())) {
Notifications.error("Invalid input");
return;
}
removeRemove(room);
Notifications.success("Room <b>" + room.id() + "</b> was successfully removed!");
removeRoom(room);
Notifications.success("Room <b>" + room.getIdentifier() + "</b> was successfully removed!");
dialog.close();
});
@ -176,15 +174,17 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
dialog.open();
}
private void removeRemove(RRoom.Short room) {
if (removeMethod != null) {
removeMethod.accept(room);
}
public void removeRoom(ShortResponseRoom room) {
roomRepository.removeRoom(room);
refresh();
}
private int getUsers(RRoom.Short room) {
private int getUsers(ShortResponseRoom room) {
var users = room.getDetail(RoomDetails.COUNT_USERS);
if (users == null) return 0;
try {
return Integer.parseInt(room.details().getOrDefault(RoomDetails.COUNT_USERS, "0"));
return Integer.parseInt(users);
} catch (NumberFormatException ex) {
return 0;
}
@ -192,7 +192,7 @@ public class RoomList extends VerticalLayout implements RefreshableTable {
@Override
public void refresh() {
cachedRooms = roomRepository.all(node, RoomRepository.ALL_DETAILS);
cachedRooms = roomRepository.allRooms(GetAllRooms.withAllDetails(node.getIdentifierObject()));
applySearch(searchField.getValue());
int users = 0;

View File

@ -9,25 +9,29 @@ import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.model.user.UserDetails;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.request.user.GetAllUsersFromRoom;
import ru.dragonestia.picker.api.repository.request.user.UnlinkUsersFromRoom;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class UserList extends VerticalLayout implements RefreshableTable {
private final RRoom room;
private final IRoom room;
private final UserRepository userRepository;
private final Button buttonRemove;
private final Grid<RUser> usersGrid;
private final Grid<IUser> usersGrid;
private final Span totalUsers = new Span();
private final Span occupancy = new Span();
private List<RUser> cachedUsers = new ArrayList<>();
private List<IUser> cachedUsers = new ArrayList<>();
public UserList(RRoom room, UserRepository userRepository) {
public UserList(IRoom room, UserRepository userRepository) {
this.room = room;
this.userRepository = userRepository;
@ -45,21 +49,25 @@ public class UserList extends VerticalLayout implements RefreshableTable {
button.addClickListener(event -> {
var users = usersGrid.getSelectedItems();
if (users.isEmpty()) return;
userRepository.unlinkFromRoom(room, users);
userRepository.unlinkUsersFromRoom(UnlinkUsersFromRoom.builder()
.setNodeId(room.getNodeIdentifierObject())
.setRoomId(room.getIdentifierObject())
.setUsers(users.stream().map(IUser::getIdentifierObject).collect(Collectors.toSet()))
.build());
refresh();
});
return button;
}
private Grid<RUser> createUsersGrid() {
var grid = new Grid<RUser>();
private Grid<IUser> createUsersGrid() {
var grid = new Grid<IUser>();
grid.addColumn(RUser::getId).setHeader("User Identifier").setSortable(true).setFooter(totalUsers);
grid.addColumn(IUser::getIdentifier).setHeader("User Identifier").setSortable(true).setFooter(totalUsers);
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setTextAlign(ColumnTextAlign.CENTER)
.setHeader("Linked with rooms").setComparator((user1, user2) -> {
var r1 = Integer.parseInt(user1.getDetail(UserDetails.COUNT_ROOMS));
var r2 = Integer.parseInt(user2.getDetail(UserDetails.COUNT_ROOMS));
var r1 = Integer.parseInt(Objects.requireNonNull(user1.getDetail(UserDetails.COUNT_ROOMS)));
var r2 = Integer.parseInt(Objects.requireNonNull(user2.getDetail(UserDetails.COUNT_ROOMS)));
return Integer.compare(r1, r2);
}).setSortable(true).setFooter(occupancy);
@ -73,11 +81,11 @@ public class UserList extends VerticalLayout implements RefreshableTable {
return grid;
}
private Button createManageButton(RUser user) {
private Button createManageButton(IUser user) {
var button = new Button("Details");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(e -> {
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getId()));
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getIdentifier()));
});
return button;
}
@ -106,16 +114,17 @@ public class UserList extends VerticalLayout implements RefreshableTable {
}
public static int getUsingPercentage(int slots, int usedSlots) {
if (slots == RRoom.INFINITE_SLOTS) return -1;
if (slots == IRoom.UNLIMITED_SLOTS) return -1;
double percent = usedSlots / (double) slots * 100;
return (int) percent;
}
@Override
public void refresh() {
cachedUsers = userRepository.all(room, UserRepository.ALL_DETAILS);
cachedUsers = userRepository.getAllUsersFormRoom(GetAllUsersFromRoom.withAllDetails(room.getNodeIdentifierObject(), room.getIdentifierObject()))
.stream().map(user -> (IUser) user).toList();
usersGrid.setItems(cachedUsers);
totalUsers.setText("Total users: " + cachedUsers.size());
occupancy.setText("Occupancy: %s".formatted(getUsingPercentage(room.getSlots(), cachedUsers.size()) + "%"));
occupancy.setText("Occupancy: %s".formatted(getUsingPercentage(room.getMaxSlots(), cachedUsers.size()) + "%"));
}
}

View File

@ -0,0 +1,25 @@
package ru.dragonestia.picker.cp.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.cp.annotation.ServerURL;
@Configuration
public class RoomPickerConfig {
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080}")
private String serverUrl;
@ServerURL
@Bean
String severUrl() {
return serverUrl;
}
@Bean
RoomPickerClient roomPickerClient() {
return new RoomPickerClient(serverUrl, "test", "test");
}
}

View File

@ -1,19 +0,0 @@
package ru.dragonestia.picker.cp.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.URI;
@Configuration
public class ServerConfig {
@Value("${ROOMPICKER_HOST_URL:http://localhost:8080/}")
private String serverUrl;
@Bean
URI serverUrl() {
return URI.create(serverUrl);
}
}

View File

@ -9,18 +9,18 @@ import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
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.impl.RoomPickerClient;
@Route("/login")
public class LoginPage extends VerticalLayout {
private final UserRepository userRepository;
private final RoomPickerClient client;
private final TextField fieldLogin;
private final PasswordField fieldPassword;
@Autowired
public LoginPage(UserRepository userRepository) {
this.userRepository = userRepository;
public LoginPage(RoomPickerClient client) {
this.client = client;
setAlignItems(Alignment.CENTER);

View File

@ -10,20 +10,20 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import org.springframework.beans.factory.annotation.Qualifier;
import ru.dragonestia.picker.api.repository.RoomPickerRepository;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.repository.response.RoomPickerInfoResponse;
import java.net.URI;
import ru.dragonestia.picker.cp.annotation.ServerURL;
public class MainLayout extends AppLayout {
private final RoomPickerInfoResponse info;
private final RoomPickerClient client;
private final RoomPickerInfoResponse serverInfo;
private final String serverUrl;
public MainLayout(RoomPickerRepository roomPickerRepository, URI serverUrl) {
info = roomPickerRepository.getInfo();
this.serverUrl = serverUrl.toString();
public MainLayout(RoomPickerClient client, @ServerURL String serverUrl) {
this.client = client;
this.serverInfo = client.getServerInfo();
this.serverUrl = serverUrl;
var toggle = new DrawerToggle();
var scroller = new Scroller(createSideNav());
@ -37,7 +37,7 @@ public MainLayout(RoomPickerRepository roomPickerRepository, URI serverUrl) {
layout.setAlignItems(FlexComponent.Alignment.END);
layout.setPadding(true);
layout.add(new Html("<h2><u>RoomPicker!</u></h2>"));
layout.add(new Html("<sub>" + info.version() + "</sub>"));
layout.add(new Html("<sub>" + serverInfo.version() + "</sub>"));
return layout;
}

View File

@ -10,9 +10,8 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.NodeRepository;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.cp.component.RoomList;
import ru.dragonestia.picker.cp.component.NavPath;
import ru.dragonestia.picker.cp.component.RegisterRoom;
@ -24,28 +23,27 @@ import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
@Route(value = "/nodes/:nodeId", layout = MainLayout.class)
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final RoomPickerClient client;
private final RouteParamsExtractor paramsExtractor;
private RNode node;
private INode node;
private RegisterRoom registerRoom;
private RoomList roomList;
@Override
public void beforeEnter(BeforeEnterEvent event) {
node = paramsExtractor.extractNodeId(event);
node = paramsExtractor.extractNode(event);
initComponents(node);
}
private void initComponents(RNode node) {
add(NavPath.toNode(node.getId()));
private void initComponents(INode node) {
add(NavPath.toNode(node.getIdentifier()));
printNodeDetails(node);
add(new Hr());
add(registerRoom = new RegisterRoom(node, (room, persist) -> {
add(registerRoom = new RegisterRoom(node, roomDefinition -> {
try {
roomRepository.register(room, persist);
client.getRoomRepository().saveRoom(roomDefinition);
return new RegisterRoom.Response(false, null);
} catch (Error error) {
return new RegisterRoom.Response(true, error.getMessage());
@ -54,19 +52,15 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
}
}));
add(new Hr());
add(roomList = new RoomList(node, roomRepository));
roomList.setRemoveMethod(room -> {
roomRepository.remove(node, room);
roomList.refresh();
});
add(roomList = new RoomList(node, client.getRoomRepository()));
}
private void printNodeDetails(RNode node) {
private void printNodeDetails(INode node) {
add(new H2("Node details"));
var layout = new VerticalLayout();
layout.add(new Html("<span>Identifier: <b>" + node.getId() + "</b></span>"));
layout.add(new Html("<span>Mode: <b>" + node.getMode().getName() + "</b></span>"));
layout.add(new Html("<span>Identifier: <b>" + node.getIdentifier() + "</b></span>"));
layout.add(new Html("<span>Mode: <b>" + node.getPickingMethod().name() + "</b></span>"));
add(layout);
}

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import ru.dragonestia.picker.api.exception.ApiException;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.repository.NodeRepository;
import ru.dragonestia.picker.cp.component.NavPath;
import ru.dragonestia.picker.cp.component.NodeList;
@ -23,24 +24,20 @@ public class NodesPage extends VerticalLayout {
private final RegisterNode registerNode;
private final NodeList nodeList;
public NodesPage(@Autowired NodeRepository nodeRepository) {
public NodesPage(@Autowired RoomPickerClient client) {
super();
this.nodeRepository = nodeRepository;
this.nodeRepository = client.getNodeRepository();
add(NavPath.rootNodes());
add(registerNode = createRegisterNodeElement());
add(new Hr());
add(nodeList = createNodeListElement());
nodeList.setRemoveMethod(nodeIdentifier -> {
nodeRepository.remove(nodeIdentifier);
nodeList.refresh();
});
}
protected RegisterNode createRegisterNodeElement() {
return new RegisterNode((node, persist) -> {
return new RegisterNode(nodeDefinition -> {
try {
nodeRepository.register(node, persist);
nodeRepository.saveNode(nodeDefinition);
return new RegisterNode.Response(false, "");
} catch (ApiException ex) {
return new RegisterNode.Response(true, ex.getMessage());

View File

@ -14,12 +14,12 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.NodeRepository;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.room.ResponseRoom;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.repository.request.user.LinkUsersWithRoom;
import ru.dragonestia.picker.cp.component.AddUsers;
import ru.dragonestia.picker.cp.component.NavPath;
import ru.dragonestia.picker.cp.component.Notifications;
@ -28,19 +28,18 @@ import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@PageTitle("Room details")
@Route(value = "/nodes/:nodeId/rooms/:roomId", layout = MainLayout.class)
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final UserRepository userRepository;
private final RoomPickerClient client;
private final RouteParamsExtractor paramsExtractor;
private RNode node;
private RRoom room;
private INode node;
private ResponseRoom room;
private AddUsers addUsers;
private UserList userList;
private Button lockRoomButton;
@ -48,28 +47,28 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
@Override
public void beforeEnter(BeforeEnterEvent event) {
node = paramsExtractor.extractNodeId(event);
room = paramsExtractor.extractRoomId(event, node);
node = paramsExtractor.extractNode(event);
room = (ResponseRoom) paramsExtractor.extractRoom(event, node);
init();
}
private void init() {
add(NavPath.toRoom(node.getId(), room.getId()));
add(NavPath.toRoom(node.getIdentifier(), room.getIdentifier()));
add(new H2("Room details"));
printRoomDetails();
add(new Hr());
add(addUsers = new AddUsers(room, (users, ignoreLimitation) -> appendUsers(room, users, ignoreLimitation)));
add(new Hr());
add(new H2("Users"));
add(userList = new UserList(room, userRepository));
add(userList = new UserList(room, client.getUserRepository()));
}
private void updateRoomInfo() {
roomInfo.removeAll();
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getNodeId() + "</b></span>"));
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getId() + "</b></span>"));
roomInfo.add(new Html("<span>Slots: <b>" + (room.isUnlimited()? "Unlimited" : room.getSlots()) + "</b></span>"));
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getNodeIdentifier() + "</b></span>"));
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getIdentifier() + "</b></span>"));
roomInfo.add(new Html("<span>Slots: <b>" + (room.hasUnlimitedSlots()? "Unlimited" : room.getMaxSlots()) + "</b></span>"));
roomInfo.add(new Html("<span>Locked: <b>" + (room.isLocked()? "Yes" : "No") + "</b></span>"));
}
@ -100,7 +99,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
private void changeBucketLockedState() {
var newValue = !room.isLocked();
roomRepository.lock(room, newValue);
client.getRoomRepository().lockRoom(room.getPath(), newValue);
room.setLocked(newValue);
setLockRoomButtonState();
@ -109,12 +108,12 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
Notifications.success("Success");
}
private void appendUsers(RRoom room, Collection<RUser> users, boolean ignoreLimitation) {
private void appendUsers(IRoom room, Collection<IUser> users, boolean ignoreLimitation) {
AtomicBoolean validationFail = new AtomicBoolean(false);
var newUsers = users.stream()
.filter(user -> {
if (user.getId().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
if (user.getIdentifier().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
return true;
}
@ -122,7 +121,11 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
return false;
}).toList();
userRepository.linkWithRoom(room, newUsers, ignoreLimitation);
client.getUserRepository().linkUsersWithRoom(LinkUsersWithRoom.builder()
.setRoomId(room.getIdentifierObject())
.setUsers(users.stream().map(IUser::getIdentifierObject).collect(Collectors.toSet()))
.setIgnoreSlotLimitation(ignoreLimitation)
.build());
userList.refresh();
if (validationFail.get()) {

View File

@ -13,54 +13,55 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.repository.request.user.FindRoomsLinkedWithUser;
import ru.dragonestia.picker.cp.component.RefreshableTable;
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@RequiredArgsConstructor
@PageTitle("User details")
@Route(value = "/users/:userId", layout = MainLayout.class)
public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserver, RefreshableTable {
private final UserRepository userRepository;
private final RoomPickerClient client;
private final RouteParamsExtractor paramsExtractor;
private RUser user;
private Grid<RRoom.Short> gridRooms;
private List<RRoom.Short> cachedRooms = new LinkedList<>();
private IUser user;
private Grid<ShortResponseRoom> gridRooms;
private List<ShortResponseRoom> cachedRooms = new LinkedList<>();
@Override
public void beforeEnter(BeforeEnterEvent event) {
user = paramsExtractor.extractUserId(event);
user = paramsExtractor.extractUser(event);
init();
}
private void init() {
add(new H2("User '%s'".formatted(user.getId())));
add(new H2("User '%s'".formatted(user.getIdentifier())));
add(new H3("Linked with rooms"));
add(gridRooms = createGrid());
refresh();
}
private Grid<RRoom.Short> createGrid() {
var grid = new Grid<RRoom.Short>();
private Grid<ShortResponseRoom> createGrid() {
var grid = new Grid<ShortResponseRoom>();
grid.addColumn(RRoom.Short::id).setHeader("Room identifier").setSortable(true);
grid.addColumn(ShortResponseRoom::getIdentifier).setHeader("Room identifier").setSortable(true);
grid.addColumn(RRoom.Short::nodeId).setHeader("Node identifier").setSortable(true);
grid.addColumn(ShortResponseRoom::getNodeIdentifier).setHeader("Node identifier").setSortable(true);
grid.addColumn(room -> room.details().get(RoomDetails.COUNT_USERS)).setHeader("Users")
grid.addColumn(room -> room.getDetail(RoomDetails.COUNT_USERS)).setHeader("Users")
.setComparator((room1, room2) -> {
var r1 = Integer.parseInt(room1.details().get(RoomDetails.COUNT_USERS));
var r2 = Integer.parseInt(room2.details().get(RoomDetails.COUNT_USERS));
var r1 = Integer.parseInt(Objects.requireNonNull(room1.getDetail(RoomDetails.COUNT_USERS)));
var r2 = Integer.parseInt(Objects.requireNonNull(room2.getDetail(RoomDetails.COUNT_USERS)));
return Integer.compare(r1, r2);
}).setSortable(true);
@ -69,7 +70,7 @@ public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserv
var button = new Button("Details");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(event -> {
getUI().ifPresent(ui -> ui.navigate("/nodes/%s/rooms/%s".formatted(room.nodeId(), room.id())));
getUI().ifPresent(ui -> ui.navigate("/nodes/%s/rooms/%s".formatted(room.getNodeIdentifier(), room.getIdentifier())));
});
return button;
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
@ -84,7 +85,8 @@ public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserv
@Override
public void refresh() {
cachedRooms = userRepository.getLinkedRoomsWithUsers(user, RoomRepository.ALL_DETAILS);
cachedRooms = client.getUserRepository()
.findRoomsLinkedWithUser(FindRoomsLinkedWithUser.withAllDetails(user.getIdentifierObject()));
gridRooms.setItems(cachedRooms);
}
}

View File

@ -14,13 +14,17 @@ 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.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.model.user.UserDetails;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.request.user.SearchUsers;
import ru.dragonestia.picker.api.repository.type.UserIdentifier;
import ru.dragonestia.picker.cp.component.RefreshableTable;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@PageTitle("Search users")
@Route(value = "/users", layout = MainLayout.class)
@ -28,13 +32,13 @@ public class UserSearchPage extends VerticalLayout implements RefreshableTable {
private final UserRepository userRepository;
private final TextField fieldUsername;
private final Grid<RUser> userGrid;
private final Grid<IUser> userGrid;
private final Span foundUsers;
private List<RUser> cachedUsers = new LinkedList<>();
private List<IUser> cachedUsers = new LinkedList<>();
@Autowired
public UserSearchPage(UserRepository userRepository) {
this.userRepository = userRepository;
public UserSearchPage(RoomPickerClient client) {
this.userRepository = client.getUserRepository();
foundUsers = new Span();
add(fieldUsername = createUsernameInputField());
@ -60,15 +64,15 @@ public class UserSearchPage extends VerticalLayout implements RefreshableTable {
return field;
}
private Grid<RUser> createUserGrid() {
var grid = new Grid<RUser>();
private Grid<IUser> createUserGrid() {
var grid = new Grid<IUser>();
grid.addColumn(RUser::getId).setHeader("Identifier").setSortable(true)
grid.addColumn(IUser::getIdentifier).setHeader("Identifier").setSortable(true)
.setFooter(foundUsers);
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setComparator((user1, user2) -> {
var r1 = Integer.parseInt(user1.getDetail(UserDetails.COUNT_ROOMS));
var r2 = Integer.parseInt(user2.getDetail(UserDetails.COUNT_ROOMS));
var r1 = Integer.parseInt(Objects.requireNonNull(user1.getDetail(UserDetails.COUNT_ROOMS)));
var r2 = Integer.parseInt(Objects.requireNonNull(user2.getDetail(UserDetails.COUNT_ROOMS)));
return Integer.compare(r1, r2);
}).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms");
@ -77,7 +81,7 @@ public class UserSearchPage extends VerticalLayout implements RefreshableTable {
var button = new Button("Details");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(event -> {
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getId()));
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getIdentifier()));
});
return button;
}).setTextAlign(ColumnTextAlign.END).setFrozenToEnd(true).setHeader(createRefreshButton());
@ -87,7 +91,8 @@ public class UserSearchPage extends VerticalLayout implements RefreshableTable {
}
private void search(String input) {
userGrid.setItems(cachedUsers = userRepository.search(input, UserRepository.ALL_DETAILS));
userGrid.setItems(cachedUsers = userRepository.searchUsers(SearchUsers.withAllDetails(UserIdentifier.of(input)))
.stream().map(user -> (IUser) user).toList());
}
@Override

View File

@ -1,57 +0,0 @@
package ru.dragonestia.picker.cp.repository.impl;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpMethod;
import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException;
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.model.node.NodeDetails;
import ru.dragonestia.picker.api.repository.response.NodeDetailsResponse;
import ru.dragonestia.picker.api.repository.response.NodeListResponse;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.NodeRepository;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Log4j2
@RequiredArgsConstructor
@SpringComponent
public class NodeRepositoryImpl implements NodeRepository {
private final RestUtil rest;
@Override
public void register(RNode node, boolean persist) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
rest.query("nodes", HttpMethod.POST, params -> {
params.put("nodeId", node.getId());
params.put("method", node.getMode().name());
params.put("persist", Boolean.toString(persist));
});
}
@Override
public List<RNode> all(Set<NodeDetails> details) {
return rest.query("nodes", HttpMethod.GET, NodeListResponse.class, params -> {
params.put("requiredDetails", String.join(",", details.stream().map(Enum::toString).toList()));
}).nodes();
}
@Override
public Optional<RNode> find(String nodeId) {
try {
var response = rest.query("nodes/" + nodeId, HttpMethod.GET, NodeDetailsResponse.class, params -> {});
return Optional.of(response.node());
} catch (NodeNotFoundException ex) {
return Optional.empty();
}
}
@Override
public void remove(String nodeId) {
rest.query("nodes/" + nodeId, HttpMethod.DELETE, params -> {});
}
}

View File

@ -1,74 +0,0 @@
package ru.dragonestia.picker.cp.repository.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import ru.dragonestia.picker.api.exception.ExceptionFactory;
import ru.dragonestia.picker.api.repository.response.ErrorResponse;
import java.net.URI;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
@Component
@RequiredArgsConstructor
public class RestUtil {
private final URI serverUrl;
private final Supplier<RestTemplate> restTemplateSupplier;
public void query(String uri, HttpMethod method) {
query(uri, method, ParamsConsumer.NONE);
}
public void query(String uri, HttpMethod method, ParamsConsumer paramsConsumer) {
var params = new HashMap<String, String>();
paramsConsumer.accept(params);
var template = restTemplateSupplier.get();
try {
template.exchange(buildPath(uri, params.keySet()), method, null, String.class, params);
} catch (HttpClientErrorException ex) {
throw ExceptionFactory.of(Objects.requireNonNull(ex.getResponseBodyAs(ErrorResponse.class)));
}
}
public <T> T query(String uri, HttpMethod method, Class<T> clazz) {
return query(uri, method, clazz, ParamsConsumer.NONE);
}
public <T> T query(String uri, HttpMethod method, Class<T> clazz, ParamsConsumer paramsConsumer) {
var params = new HashMap<String, String>();
paramsConsumer.accept(params);
var template = restTemplateSupplier.get();
try {
return template.exchange(buildPath(uri, params.keySet()), method, null, clazz, params).getBody();
} catch (HttpClientErrorException ex) {
throw ExceptionFactory.of(Objects.requireNonNull(ex.getResponseBodyAs(ErrorResponse.class)));
}
}
private String buildPath(String uri, Collection<String> paramKeys) {
var path = new StringBuilder(serverUrl.resolve(uri) + "?");
int left = paramKeys.size();
for (var key: paramKeys) {
path.append(key);
path.append("={");
path.append(key);
path.append("}");
if (--left > 0) {
path.append("&");
}
}
return path.toString();
}
public interface ParamsConsumer extends Consumer<Map<String, String>> {
ParamsConsumer NONE = map -> {};
}
}

View File

@ -1,19 +0,0 @@
package ru.dragonestia.picker.cp.repository.impl;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpMethod;
import ru.dragonestia.picker.api.repository.RoomPickerRepository;
import ru.dragonestia.picker.api.repository.response.RoomPickerInfoResponse;
@RequiredArgsConstructor
@SpringComponent
public class RoomPickerRepositoryImpl implements RoomPickerRepository {
private final RestUtil rest;
@Override
public RoomPickerInfoResponse getInfo() {
return rest.query("/info", HttpMethod.GET, RoomPickerInfoResponse.class, params -> {});
}
}

View File

@ -1,73 +0,0 @@
package ru.dragonestia.picker.cp.repository.impl;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpMethod;
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.repository.response.RoomInfoResponse;
import ru.dragonestia.picker.api.repository.response.RoomListResponse;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Log4j2
@RequiredArgsConstructor
@SpringComponent
public class RoomRepositoryImpl implements RoomRepository {
private final RestUtil rest;
@Override
public void register(RRoom room, boolean persist) throws NodeNotFoundException, InvalidRoomIdentifierException, RoomAlreadyExistException {
rest.query("/nodes/" + room.getNodeId() + "/rooms", HttpMethod.POST, params -> {
params.put("roomId", room.getId());
params.put("slots", Integer.toString(room.getSlots()));
params.put("payload", room.getPayload());
params.put("locked", Boolean.toString(room.isLocked()));
params.put("persist", Boolean.toString(persist));
});
}
@Override
public void remove(RRoom room) throws NodeNotFoundException {
rest.query("/nodes/" + room.getNodeId() + "/rooms/" + room.getId(), HttpMethod.DELETE, params -> {});
}
@Override
public void remove(RNode node, RRoom.Short room) throws NodeNotFoundException {
rest.query("/nodes/" + node.getId() + "/rooms/" + room.id(), HttpMethod.DELETE, params -> {});
}
@Override
public List<RRoom.Short> all(RNode node, Set<RoomDetails> details) throws NodeNotFoundException {
return rest.query("/nodes/" + node.getId() + "/rooms", HttpMethod.GET, RoomListResponse.class, params -> {
params.put("requiredDetails", String.join(",", details.stream().map(Enum::toString).toList()));
}).rooms();
}
@Override
public Optional<RRoom> find(RNode node, String roomId) throws NodeNotFoundException {
try {
var response = rest.query("/nodes/" + node.getId() + "/rooms/" + roomId, HttpMethod.GET, RoomInfoResponse.class, map -> {});
return Optional.of(response.room());
} catch (RoomNotFoundException ex) {
return Optional.empty();
}
}
@Override
public void lock(RRoom room, boolean value) throws NodeNotFoundException, RoomNotFoundException {
rest.query("/nodes/%s/rooms/%s/lock".formatted(room.getNodeId(), room.getId()), HttpMethod.PUT, params -> {
params.put("newState", Boolean.toString(value));
});
}
}

View File

@ -1,96 +0,0 @@
package ru.dragonestia.picker.cp.repository.impl;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpMethod;
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.model.room.RoomDetails;
import ru.dragonestia.picker.api.model.user.UserDetails;
import ru.dragonestia.picker.api.repository.response.LinkedRoomsWithUserResponse;
import ru.dragonestia.picker.api.repository.response.SearchUserResponse;
import ru.dragonestia.picker.api.repository.response.UserDetailsResponse;
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;
import ru.dragonestia.picker.api.repository.response.RoomUserListResponse;
import java.util.*;
@Log4j2
@RequiredArgsConstructor
@SpringComponent
public class UserRepositoryImpl implements UserRepository {
private final RestUtil rest;
@Override
public void linkWithRoom(RRoom room, Collection<RUser> users, boolean force) throws NodeNotFoundException, RoomNotFoundException, RoomAreFullException {
rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
HttpMethod.POST,
params -> {
params.put("userIds", String.join(",", users.stream().map(RUser::getId).toList()));
params.put("force", Boolean.toString(force));
});
}
@Override
public void unlinkFromRoom(RRoom room, Collection<RUser> users) throws NodeNotFoundException, RoomNotFoundException {
rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
HttpMethod.DELETE,
params -> {
params.put("userIds", String.join(",", users.stream().map(RUser::getId).toList()));
});
}
@Override
public List<RUser> all(RRoom room, Set<UserDetails> details) throws NodeNotFoundException, RoomNotFoundException {
return rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
HttpMethod.GET,
RoomUserListResponse.class,
params -> {
var detailsStr = String.join(",", details.stream().map(Enum::toString).toList());
params.put("requiredDetails", detailsStr);
}).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();
}
@Override
public RUser find(String userId, Set<UserDetails> details) {
return rest.query("/users/" + userId,
HttpMethod.GET,
UserDetailsResponse.class,
params -> {
var detailsStr = String.join(",", details.stream().map(Enum::toString).toList());
params.put("requiredDetails", detailsStr);
}).user();
}
@Override
public List<RRoom.Short> getLinkedRoomsWithUsers(RUser user, Set<RoomDetails> details) {
return rest.query("/users/" + user.getId() + "/rooms",
HttpMethod.GET,
LinkedRoomsWithUserResponse.class,
params -> {
var detailsStr = String.join(",", details.stream().map(Enum::toString).toList());
params.put("requiredDetails", detailsStr);
}).rooms(); // TODO
}
}

View File

@ -5,33 +5,36 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.NodeRepository;
import ru.dragonestia.picker.api.repository.RoomRepository;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.impl.RoomPickerClient;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.repository.request.node.FindNodeById;
import ru.dragonestia.picker.api.repository.request.room.FindRoomById;
import ru.dragonestia.picker.api.repository.request.user.FindUserById;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.api.repository.type.UserIdentifier;
@Component
@RequiredArgsConstructor
public class RouteParamsExtractor {
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final UserRepository userRepository;
private final RoomPickerClient client;
public RNode extractNodeId(BeforeEnterEvent e) throws NodeNotFoundException {
var nodeId = e.getRouteParameters().get("nodeId").orElseThrow(() -> new NodeNotFoundException("null"));
return nodeRepository.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
public INode extractNode(BeforeEnterEvent e) throws NodeNotFoundException {
var nodeId = NodeIdentifier.of(e.getRouteParameters().get("nodeId").orElseThrow(() -> new NodeNotFoundException("null")));
return client.getNodeRepository().findNodeById(FindNodeById.justFind(nodeId)).orElseThrow(() -> new NodeNotFoundException(nodeId.getValue()));
}
public RRoom extractRoomId(BeforeEnterEvent e, RNode node) throws RoomNotFoundException {
var roomId = e.getRouteParameters().get("roomId").orElseThrow(() -> new NodeNotFoundException("null"));
return roomRepository.find(node, roomId).orElseThrow(() -> new NodeNotFoundException(roomId));
public IRoom extractRoom(BeforeEnterEvent e, INode node) throws RoomNotFoundException {
var nodeId = node.getIdentifierObject();
var roomId = RoomIdentifier.of(e.getRouteParameters().get("roomId").orElseThrow(() -> new NodeNotFoundException("null")));
return client.getRoomRepository().find(FindRoomById.just(nodeId, roomId)).orElseThrow(() -> new NodeNotFoundException(roomId.getValue()));
}
public RUser extractUserId(BeforeEnterEvent e) {
var userId = e.getRouteParameters().get("userId").orElseThrow(RuntimeException::new);
return userRepository.find(userId, UserRepository.ALL_DETAILS);
public IUser extractUser(BeforeEnterEvent e) {
var userId = UserIdentifier.of(e.getRouteParameters().get("userId").orElseThrow(RuntimeException::new));
return client.getUserRepository().findUserById(FindUserById.withAllDetails(userId));
}
}