Implemented user details page

This commit is contained in:
Andrey Terentev 2024-02-05 13:57:07 +07:00 committed by Andrey Terentev
parent be203ccf5c
commit 3b8fa87c5b
19 changed files with 331 additions and 62 deletions

View File

@ -3,12 +3,14 @@ package ru.dragonestia.picker.api.repository;
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.details.RoomDetails;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.details.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public interface UserRepository {
@ -26,4 +28,8 @@ public interface UserRepository {
List<RUser> all(RRoom room, Set<UserDetails> details) throws NodeNotFoundException, RoomNotFoundException;
List<RUser> search(String input, Set<UserDetails> details);
RUser find(String userId, Set<UserDetails> details);
List<RRoom.Short> getLinkedRoomsWithUsers(RUser user, Set<RoomDetails> roomDetails);
}

View File

@ -0,0 +1,7 @@
package ru.dragonestia.picker.api.repository.response;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import java.util.List;
public record LinkedRoomsWithUserResponse(List<RRoom.Short> rooms) {}

View File

@ -0,0 +1,5 @@
package ru.dragonestia.picker.api.repository.response;
import ru.dragonestia.picker.api.repository.response.type.RUser;
public record UserDetailsResponse(RUser user) {}

View File

@ -87,5 +87,5 @@ public class RRoom {
return false;
}
public record Short(String id, int slots, boolean locked, Map<RoomDetails, String> details) {}
public record Short(String id, String nodeId, int slots, boolean locked, Map<RoomDetails, String> details) {}
}

View File

@ -1,16 +1,17 @@
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 org.springframework.web.bind.annotation.*;
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.RUser;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.service.UserService;
import ru.dragonestia.picker.util.DetailsParser;
import ru.dragonestia.picker.util.NamingValidator;
import java.util.HashSet;
import java.util.List;
@RequiredArgsConstructor
@ -19,6 +20,7 @@ import java.util.List;
public class UserController {
private final UserService userService;
private final DetailsParser detailsParser;
private final NamingValidator namingValidator;
@GetMapping("/search")
@ -29,13 +31,28 @@ public class UserController {
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, detailsParser.parseUserDetails(detailsSeq)));
}
@GetMapping("/{userId}")
UserDetailsResponse find(@PathVariable(value = "userId") String userId,
@RequestParam(value = "requiredDetails", required = false) String detailsSeq) {
if (!namingValidator.validateUserId(userId)) {
return new UserDetailsResponse(new RUser(userId));
}
return new SearchUserResponse(userService.searchUsers(input, details));
return new UserDetailsResponse(userService.getUserDetails(userId, detailsParser.parseUserDetails(detailsSeq)));
}
@GetMapping("/{userId}/rooms")
LinkedRoomsWithUserResponse roomsOf(@PathVariable(value = "userId") String userId,
@RequestParam(value = "requiredDetails", required = false) String detailsSeq) {
if (!namingValidator.validateUserId(userId)) {
return new LinkedRoomsWithUserResponse(List.of());
}
return new LinkedRoomsWithUserResponse(userService.getUserRoomsWithDetails(new User(userId), detailsParser.parseRoomDetails(detailsSeq)));
}
}

View File

@ -54,6 +54,6 @@ public class Room {
}
public RRoom.Short toShortResponseObject() {
return new RRoom.Short(id, slots.getSlots(), locked, new HashMap<>());
return new RRoom.Short(id, nodeId, slots.getSlots(), locked, new HashMap<>());
}
}

View File

@ -1,7 +1,6 @@
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;

View File

@ -1,6 +1,8 @@
package ru.dragonestia.picker.service;
import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.details.UserDetails;
import ru.dragonestia.picker.model.Room;
@ -14,6 +16,8 @@ public interface UserService {
List<Room> getUserRooms(User user);
List<RRoom.Short> getUserRoomsWithDetails(User user, Set<RoomDetails> details);
int linkUsersWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
void unlinkUsersFromRoom(Room room, Collection<User> users);
@ -23,4 +27,6 @@ public interface UserService {
List<RUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details);
List<RUser> searchUsers(String input, Set<UserDetails> details);
RUser getUserDetails(String userId, Set<UserDetails> details);
}

View File

@ -9,6 +9,7 @@ import ru.dragonestia.picker.api.repository.response.type.RNode;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.service.NodeService;
import ru.dragonestia.picker.util.DetailsExtractor;
import ru.dragonestia.picker.util.NamingValidator;
import java.util.LinkedList;
@ -21,6 +22,7 @@ import java.util.Set;
public class NodeServiceImpl implements NodeService {
private final NodeRepository nodeRepository;
private final DetailsExtractor detailsExtractor;
private final NamingValidator namingValidator;
@Override
@ -43,13 +45,7 @@ public class NodeServiceImpl implements NodeService {
public List<RNode> getAllNodesWithDetailsResponse(Set<NodeDetails> details) {
var response = new LinkedList<RNode>();
for (var node: all()) {
var responseNode = node.toResponseObject();
for (var detail: details) {
// TODO...
}
response.add(responseNode);
response.add(detailsExtractor.extract(node, details));
}
return response;
}

View File

@ -6,14 +6,13 @@ import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.details.UserDetails;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.Node;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.UserRepository;
import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.util.DetailsExtractor;
import ru.dragonestia.picker.util.NamingValidator;
import java.util.LinkedList;
@ -27,7 +26,7 @@ import java.util.Set;
public class RoomServiceImpl implements RoomService {
private final RoomRepository roomRepository;
private final UserRepository userRepository;
private final DetailsExtractor detailsExtractor;
private final NamingValidator namingValidator;
@Override
@ -55,16 +54,7 @@ public class RoomServiceImpl implements RoomService {
public List<RRoom.Short> getAllRoomsWithDetailsResponse(Node node, Set<RoomDetails> details) {
var response = new LinkedList<RRoom.Short>();
for (var room: all(node)) {
var responseRoom = room.toShortResponseObject();
for (var detail: details) {
if (detail == RoomDetails.COUNT_USERS) {
var users = Integer.toString(userRepository.usersOf(room).size());
responseRoom.details().put(RoomDetails.COUNT_USERS, users);
}
}
response.add(responseRoom);
response.add(detailsExtractor.extract(room, details));
}
return response;
}

View File

@ -2,12 +2,15 @@ package ru.dragonestia.picker.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.api.repository.details.UserDetails;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.UserRepository;
import ru.dragonestia.picker.service.UserService;
import ru.dragonestia.picker.util.DetailsExtractor;
import java.util.*;
import java.util.function.Function;
@ -17,6 +20,7 @@ import java.util.function.Function;
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final DetailsExtractor detailsExtractor;
private final Map<UserDetails, Function<User, String>> detailsMap = new HashMap<>();
@Override
@ -24,6 +28,15 @@ public class UserServiceImpl implements UserService {
return userRepository.findAllLinkedUserRooms(user);
}
@Override
public List<RRoom.Short> getUserRoomsWithDetails(User user, Set<RoomDetails> details) {
var result = new LinkedList<RRoom.Short>();
for (var room: getUserRooms(user)) {
result.add(detailsExtractor.extract(room, details));
}
return result;
}
@Override
public int linkUsersWithRoom(Room room, Collection<User> users, boolean force) {
userRepository.linkWithRoom(room, users, force);
@ -44,32 +57,18 @@ public class UserServiceImpl implements UserService {
public List<RUser> getRoomUsersWithDetailsResponse(Room room, Set<UserDetails> details) {
var users = new LinkedList<RUser>();
for (var user: getRoomUsers(room)) {
var responseUser = user.toResponseObject();
for (var detail: details) {
if (detail == UserDetails.COUNT_ROOMS) {
responseUser.putDetail(UserDetails.COUNT_ROOMS, Integer.toString(getUserRooms(user).size()));
}
}
users.add(responseUser);
users.add(detailsExtractor.extract(user, details));
}
return users;
}
@Override
public List<RUser> searchUsers(String input, Set<UserDetails> details) {
return userRepository.search(input).stream()
.map(user -> {
var responseUser = user.toResponseObject();
return userRepository.search(input).stream().map(user -> detailsExtractor.extract(user, details)).toList();
}
for (var detail: details) {
if (detail == UserDetails.COUNT_ROOMS) {
responseUser.putDetail(UserDetails.COUNT_ROOMS, Integer.toString(getUserRooms(user).size()));
}
}
return responseUser;
}).toList();
@Override
public RUser getUserDetails(String userId, Set<UserDetails> details) {
return detailsExtractor.extract(new User(userId), details);
}
}

View File

@ -0,0 +1,62 @@
package ru.dragonestia.picker.util;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.repository.details.NodeDetails;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.details.UserDetails;
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.model.Node;
import ru.dragonestia.picker.model.Room;
import ru.dragonestia.picker.model.User;
import ru.dragonestia.picker.repository.NodeRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.UserRepository;
import java.util.Set;
@RequiredArgsConstructor
@Component
public class DetailsExtractor {
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final UserRepository userRepository;
public RNode extract(Node node, Set<NodeDetails> details) {
var response = node.toResponseObject();
for (var detail: details) {
// TODO...
}
return response;
}
public RRoom.Short extract(Room room, Set<RoomDetails> details) {
var response = room.toShortResponseObject();
for (var detail: details) {
if (detail == RoomDetails.COUNT_USERS) {
var users = Integer.toString(userRepository.usersOf(room).size());
response.details().put(RoomDetails.COUNT_USERS, users);
}
}
return response;
}
public RUser extract(User user, Set<UserDetails> details) {
var response = user.toResponseObject();
for (var detail: details) {
if (detail == UserDetails.COUNT_ROOMS) {
response.putDetail(UserDetails.COUNT_ROOMS, Integer.toString(userRepository.findAllLinkedUserRooms(user).size()));
}
}
return response;
}
}

View File

@ -0,0 +1,43 @@
package ru.dragonestia.picker.util;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.repository.details.NodeDetails;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.details.UserDetails;
import java.util.HashSet;
import java.util.Set;
@Component
public class DetailsParser {
public Set<NodeDetails> parseNodeDetails(String detailsSeq) {
var details = new HashSet<NodeDetails>();
for (var detailStr: detailsSeq.split(",")) {
try {
details.add(NodeDetails.valueOf(detailStr.toUpperCase()));
} catch (IllegalArgumentException ignore) {}
}
return details;
}
public Set<RoomDetails> parseRoomDetails(String detailsSeq) {
var details = new HashSet<RoomDetails>();
for (var detailStr: detailsSeq.split(",")) {
try {
details.add(RoomDetails.valueOf(detailStr.toUpperCase()));
} catch (IllegalArgumentException ignore) {}
}
return details;
}
public Set<UserDetails> parseUserDetails(String detailsSeq) {
var details = new HashSet<UserDetails>();
for (var detailStr: detailsSeq.split(",")) {
try {
details.add(UserDetails.valueOf(detailStr.toUpperCase()));
} catch (IllegalArgumentException ignore) {}
}
return details;
}
}

View File

@ -1,5 +1,7 @@
package ru.dragonestia.picker.cp.component;
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;
@ -32,10 +34,19 @@ public class UserList extends VerticalLayout {
grid.addColumn(RUser::getId).setHeader("User Identifier").setFooter(totalUsers);
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms")
.setFooter(occupancy);
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
grid.addComponentColumn(this::createManageButton).setHeader("Manage");
return grid;
}
private Button createManageButton(RUser user) {
var button = new Button("Details");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(e -> {
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getId()));
});
return button;
}
public void update(List<RUser> users) {
cachedUsers = users;
usersGrid.setItems(users);

View File

@ -1,17 +1,29 @@
package ru.dragonestia.picker.cp.page;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.*;
import java.awt.*;
@Route(value = "/", layout = MainLayout.class)
public class HomePage extends VerticalLayout {
public HomePage() {
super();
add(new H1("Hello world!"));
add(new Paragraph("Hello world!"));
var field = new TextField("Some field");
field.setRequired(true);
add(field);
var button = new Button("Click me", e -> {
Notification.show(field.isInvalid() + "");
});
add(button);
}
}

View File

@ -0,0 +1,74 @@
package ru.dragonestia.picker.cp.page;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
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.RoomRepository;
import ru.dragonestia.picker.api.repository.UserRepository;
import ru.dragonestia.picker.api.repository.details.RoomDetails;
import ru.dragonestia.picker.api.repository.response.type.RRoom;
import ru.dragonestia.picker.api.repository.response.type.RUser;
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
import java.util.LinkedList;
import java.util.List;
@RequiredArgsConstructor
@PageTitle("User details")
@Route(value = "/users/:userId", layout = MainLayout.class)
public class UserDetailsPage extends VerticalLayout implements BeforeEnterObserver {
private final UserRepository userRepository;
private final RouteParamsExtractor paramsExtractor;
private RUser user;
private Grid<RRoom.Short> gridRooms;
private List<RRoom.Short> cachedRooms = new LinkedList<>();
@Override
public void beforeEnter(BeforeEnterEvent event) {
user = paramsExtractor.extractUserId(event);
init();
}
private void init() {
add(new H2("User '%s'".formatted(user.getId())));
add(new H3("Linked with rooms"));
add(gridRooms = createGrid());
update(userRepository.getLinkedRoomsWithUsers(user, RoomRepository.ALL_DETAILS));
}
private Grid<RRoom.Short> createGrid() {
var grid = new Grid<RRoom.Short>();
grid.addColumn(RRoom.Short::id).setHeader("Room identifier");
grid.addColumn(RRoom.Short::nodeId).setHeader("Node identifier");
grid.addColumn(room -> room.details().get(RoomDetails.COUNT_USERS)).setHeader("Users");
grid.addComponentColumn(room -> {
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())));
});
return button;
}).setHeader("Other");
return grid;
}
public void update(List<RRoom.Short> rooms) {
gridRooms.setItems(cachedRooms = rooms);
}
}

View File

@ -1,5 +1,6 @@
package ru.dragonestia.picker.cp.page;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
@ -49,6 +50,7 @@ public class UserSearchPage extends VerticalLayout {
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.getStyle().set("color", "#FFFFFF");
button.addClickListener(event -> search(fieldUsername.getValue().trim()));
button.addClickShortcut(Key.ENTER);
field.setSuffixComponent(button);
return field;
@ -62,7 +64,14 @@ public class UserSearchPage extends VerticalLayout {
grid.addColumn(user -> user.getDetail(UserDetails.COUNT_ROOMS)).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms");
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
grid.addComponentColumn(user -> {
var button = new Button("Details");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(event -> {
getUI().ifPresent(ui -> ui.navigate("/users/" + user.getId()));
});
return button;
}).setHeader("Manage");
return grid;
}

View File

@ -7,16 +7,17 @@ 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.repository.details.RoomDetails;
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.details.UserDetails;
import ru.dragonestia.picker.api.repository.response.RoomUserListResponse;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.*;
@Log4j2
@RequiredArgsConstructor
@ -68,4 +69,28 @@ public class UserRepositoryImpl implements UserRepository {
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,10 +5,12 @@ 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;
@Component
@RequiredArgsConstructor
@ -16,6 +18,7 @@ public class RouteParamsExtractor {
private final NodeRepository nodeRepository;
private final RoomRepository roomRepository;
private final UserRepository userRepository;
public RNode extractNodeId(BeforeEnterEvent e) throws NodeNotFoundException {
var nodeId = e.getRouteParameters().get("nodeId").orElseThrow(() -> new NodeNotFoundException("null"));
@ -26,4 +29,9 @@ public class RouteParamsExtractor {
var roomId = e.getRouteParameters().get("roomId").orElseThrow(() -> new NodeNotFoundException("null"));
return roomRepository.find(node, roomId).orElseThrow(() -> new NodeNotFoundException(roomId));
}
public RUser extractUserId(BeforeEnterEvent e) {
var userId = e.getRouteParameters().get("userId").orElseThrow(RuntimeException::new);
return userRepository.find(userId, UserRepository.ALL_DETAILS);
}
}