From 7566f24d367b080f141d78e26a237582a4210469 Mon Sep 17 00:00:00 2001 From: Andrey Terentev <54776640+ScarletRedMan@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:29:44 +0700 Subject: [PATCH] Refactor controllers and exception handling (#8) * Refactored exceptions and responses * Extracted exceptions to external api library * Refactored exceptions and extracted models to external library * Fixed test after refactor * Extracted validator to api library * Implemented session error handler * Upgraded notifications * Implemented global exception handler for control panel --- api/build.gradle | 16 +++ .../picker/api/exception/ApiException.java | 10 ++ .../api/exception/ExceptionFactory.java | 36 ++++++ .../InvalidNodeIdentifierException.java | 39 +++++++ .../InvalidRoomIdentifierException.java | 46 ++++++++ .../exception/InvalidUsernamesException.java | 50 ++++++++ .../exception/NoRoomsAvailableException.java | 39 +++++++ .../exception/NodeAlreadyExistException.java | 39 +++++++ .../api/exception/NodeNotFoundException.java | 39 +++++++ .../exception/RoomAlreadyExistException.java | 47 ++++++++ .../api/exception/RoomAreFullException.java | 47 ++++++++ .../api/exception/RoomNotFoundException.java | 47 ++++++++ .../ru/dragonestia/picker/api/model/Node.java | 39 +++++++ .../ru/dragonestia/picker/api/model/Room.java | 73 ++++++++++++ .../dragonestia/picker/api}/model/User.java | 16 ++- .../picker/api}/model/type/PickingMode.java | 15 ++- .../picker/api/repository/NodeRepository.java | 19 +++ .../picker/api/repository/RoomRepository.java | 23 ++++ .../picker/api/repository/UserRepository.java | 19 +++ .../repository/response/ErrorResponse.java | 5 + .../response/LinkUsersWithRoomResponse.java | 3 + .../response/NodeDetailsResponse.java | 5 + .../repository/response/NodeListResponse.java | 7 ++ .../repository/response/RoomInfoResponse.java | 5 + .../repository/response/RoomListResponse.java | 7 ++ .../response/RoomUserListResponse.java | 4 +- .../picker/api/utils/ValidateIdentifier.java | 18 +++ app/build.gradle | 1 + .../dragonestia/picker/config/TestConfig.java | 2 +- .../ExceptionHandlerController.java | 65 +++++++++++ .../picker/controller/NodeController.java | 75 ++++-------- .../picker/controller/RoomController.java | 100 ++++++---------- .../picker/controller/UserRoomController.java | 87 ++++---------- .../response/LinkUsersWithRoomResponse.java | 3 - .../response/NodeDetailsResponse.java | 5 - .../controller/response/NodeListResponse.java | 7 -- .../response/NodeRegisterResponse.java | 3 - .../controller/response/RoomInfoResponse.java | 5 - .../controller/response/RoomListResponse.java | 8 -- .../response/RoomRegisterResponse.java | 3 - .../ru/dragonestia/picker/model/Node.java | 6 +- .../ru/dragonestia/picker/model/Room.java | 6 + .../ru/dragonestia/picker/model/User.java | 4 + .../picker/model/type/PickingMode.java | 7 -- .../picker/repository/NodeRepository.java | 3 +- .../picker/repository/RoomRepository.java | 3 +- .../picker/repository/UserRepository.java | 3 +- .../repository/impl/NodeRepositoryImpl.java | 5 +- .../repository/impl/PickerRepository.java | 2 +- .../repository/impl/RoomRepositoryImpl.java | 5 +- .../repository/impl/UserRepositoryImpl.java | 7 +- .../impl/picker/LeastPickedPicker.java | 2 +- .../repository/impl/picker/RoomPicker.java | 2 +- .../impl/picker/RoundRobinPicker.java | 2 +- .../impl/picker/SequentialFillingPicker.java | 2 +- .../picker/service/NodeService.java | 4 +- .../picker/service/RoomService.java | 4 +- .../picker/service/UserService.java | 3 +- .../picker/service/impl/NodeServiceImpl.java | 10 +- .../picker/service/impl/RoomServiceImpl.java | 10 +- .../picker/util/NamingValidator.java | 48 ++++++-- .../picker/config/FillingNodesConfig.java | 2 +- control-panel/build.gradle | 3 + .../picker/cp/component/AddUsers.java | 10 +- .../picker/cp/component/NavPath.java | 19 ++- .../picker/cp/component/NodeList.java | 26 ++--- .../picker/cp/component/Notifications.java | 53 +++++++++ .../picker/cp/component/RegisterNode.java | 15 +-- .../picker/cp/component/RegisterRoom.java | 20 ++-- .../picker/cp/component/RoomList.java | 38 +++--- .../picker/cp/component/UserList.java | 14 ++- .../picker/cp/config/RestApiConfig.java | 2 +- .../cp/error/ApplicationErrorHandler.java | 37 ++++++ .../cp/listener/VaadinEventListener.java | 19 +++ .../ru/dragonestia/picker/cp/model/Node.java | 24 ---- .../ru/dragonestia/picker/cp/model/Room.java | 71 ----------- .../picker/cp/model/dto/RoomDTO.java | 16 --- .../picker/cp/model/type/SlotLimit.java | 21 ---- .../dragonestia/picker/cp/page/HomePage.java | 10 +- .../picker/cp/page/NodeDetailsPage.java | 58 ++++----- .../dragonestia/picker/cp/page/NodesPage.java | 10 +- .../picker/cp/page/RoomDetailsPage.java | 91 ++++----------- .../picker/cp/page/plug/ErrorPlug.java | 15 +++ .../page/plug/InvalidNodeIdentifierPlug.java | 20 ++++ .../page/plug/InvalidRoomIdentifierPlug.java | 21 ++++ .../picker/cp/page/plug/NodeNotFoundPlug.java | 20 ++++ .../picker/cp/page/plug/RoomNotFoundPlug.java | 21 ++++ .../picker/cp/repository/NodeRepository.java | 17 --- .../picker/cp/repository/RoomRepository.java | 23 ---- .../picker/cp/repository/UserRepository.java | 16 --- .../repository/impl/NodeRepositoryImpl.java | 43 +++---- .../picker/cp/repository/impl/RestUtil.java | 85 +++++--------- .../repository/impl/RoomRepositoryImpl.java | 110 +++++++----------- .../repository/impl/UserRepositoryImpl.java | 68 +++++------ .../response/LinkUsersWithRoomResponse.java | 3 - .../impl/response/NodeDetailsResponse.java | 5 - .../impl/response/NodeListResponse.java | 7 -- .../impl/response/NodeRegisterResponse.java | 3 - .../impl/response/RoomInfoResponse.java | 5 - .../impl/response/RoomListResponse.java | 7 -- .../impl/response/RoomRegisterResponse.java | 3 - .../impl/response/RoomUserListResponse.java | 7 -- .../picker/cp/util/RouteParamsExtractor.java | 29 +++++ settings.gradle | 2 + 104 files changed, 1423 insertions(+), 881 deletions(-) create mode 100644 api/build.gradle create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/ApiException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/ExceptionFactory.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/InvalidNodeIdentifierException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/InvalidRoomIdentifierException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/InvalidUsernamesException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/NoRoomsAvailableException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/NodeAlreadyExistException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/NodeNotFoundException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/RoomAlreadyExistException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/RoomAreFullException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/exception/RoomNotFoundException.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/model/Node.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/model/Room.java rename {control-panel/src/main/java/ru/dragonestia/picker/cp => api/src/main/java/ru/dragonestia/picker/api}/model/User.java (61%) rename {control-panel/src/main/java/ru/dragonestia/picker/cp => api/src/main/java/ru/dragonestia/picker/api}/model/type/PickingMode.java (51%) create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/NodeRepository.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/RoomRepository.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/ErrorResponse.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/LinkUsersWithRoomResponse.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeDetailsResponse.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeListResponse.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomInfoResponse.java create mode 100644 api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomListResponse.java rename {app/src/main/java/ru/dragonestia/picker/controller => api/src/main/java/ru/dragonestia/picker/api/repository}/response/RoomUserListResponse.java (51%) create mode 100644 api/src/main/java/ru/dragonestia/picker/api/utils/ValidateIdentifier.java create mode 100644 app/src/main/java/ru/dragonestia/picker/controller/ExceptionHandlerController.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/LinkUsersWithRoomResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/NodeDetailsResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/NodeListResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/NodeRegisterResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/RoomInfoResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/RoomListResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/controller/response/RoomRegisterResponse.java delete mode 100644 app/src/main/java/ru/dragonestia/picker/model/type/PickingMode.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/component/Notifications.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/error/ApplicationErrorHandler.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/listener/VaadinEventListener.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/model/Node.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/model/Room.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/model/dto/RoomDTO.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/SlotLimit.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/ErrorPlug.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidNodeIdentifierPlug.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidRoomIdentifierPlug.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/NodeNotFoundPlug.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/RoomNotFoundPlug.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/NodeRepository.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/RoomRepository.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/UserRepository.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/LinkUsersWithRoomResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeDetailsResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeListResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeRegisterResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomInfoResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomListResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomRegisterResponse.java delete mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomUserListResponse.java create mode 100644 control-panel/src/main/java/ru/dragonestia/picker/cp/util/RouteParamsExtractor.java diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..9b70e7e --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/ApiException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/ApiException.java new file mode 100644 index 0000000..2ec02ad --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/ApiException.java @@ -0,0 +1,10 @@ +package ru.dragonestia.picker.api.exception; + +import java.util.Map; + +public abstract class ApiException extends RuntimeException { + + public abstract String getErrorId(); + + public abstract void appendDetailsToErrorResponse(Map details); +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/ExceptionFactory.java b/api/src/main/java/ru/dragonestia/picker/api/exception/ExceptionFactory.java new file mode 100644 index 0000000..4f16c4a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/ExceptionFactory.java @@ -0,0 +1,36 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class ExceptionFactory { + + private final static Map factory = init(); + + private ExceptionFactory() {} + + private static Map init() { + var factory = new HashMap(); + + factory.put(InvalidNodeIdentifierException.ERROR_ID, InvalidNodeIdentifierException::new); + factory.put(InvalidRoomIdentifierException.ERROR_ID, InvalidRoomIdentifierException::new); + factory.put(InvalidUsernamesException.ERROR_ID, InvalidUsernamesException::new); + factory.put(NodeAlreadyExistException.ERROR_ID, NodeAlreadyExistException::new); + factory.put(NodeNotFoundException.ERROR_ID, NodeNotFoundException::new); + factory.put(NoRoomsAvailableException.ERROR_ID, NoRoomsAvailableException::new); + factory.put(RoomAlreadyExistException.ERROR_ID, RoomAlreadyExistException::new); + factory.put(RoomAreFullException.ERROR_ID, RoomAreFullException::new); + factory.put(RoomNotFoundException.ERROR_ID, RoomNotFoundException::new); + + return factory; + } + + public static ApiException of(ErrorResponse errorResponse) { + return factory.get(errorResponse.errorId()).apply(errorResponse); + } + + private interface Constructor extends Function {} +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidNodeIdentifierException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidNodeIdentifierException.java new file mode 100644 index 0000000..149478e --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidNodeIdentifierException.java @@ -0,0 +1,39 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class InvalidNodeIdentifierException extends ApiException { + + public static final String ERROR_ID = "err.node.invalid_identifier"; + + private final String nodeId; + + public InvalidNodeIdentifierException(String nodeId) { + this.nodeId = nodeId; + } + + public InvalidNodeIdentifierException(ErrorResponse errorResponse) { + this(errorResponse.details().get("identifier")); + } + + @Override + public String getMessage() { + return "Invalid node identifier. Taken '" + nodeId + "'"; + } + + public String getNodeId() { + return nodeId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("identifier", getNodeId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidRoomIdentifierException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidRoomIdentifierException.java new file mode 100644 index 0000000..4af70da --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidRoomIdentifierException.java @@ -0,0 +1,46 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class InvalidRoomIdentifierException extends ApiException { + + public static final String ERROR_ID = "err.room.invalid_identifier"; + + private final String nodeId; + private final String roomId; + + public InvalidRoomIdentifierException(String nodeId, String roomId) { + this.nodeId = nodeId; + this.roomId = roomId; + } + + public InvalidRoomIdentifierException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId"), errorResponse.details().get("roomId")); + } + + @Override + public String getMessage() { + return "Invalid room identifier. Taken '" + roomId + "'"; + } + + public String getRoomId() { + return roomId; + } + + public String getNodeId() { + return nodeId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("nodeId", getNodeId()); + details.put("roomId", getRoomId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidUsernamesException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidUsernamesException.java new file mode 100644 index 0000000..2215137 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/InvalidUsernamesException.java @@ -0,0 +1,50 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.*; +import java.util.function.Function; + +public final class InvalidUsernamesException extends ApiException { + + public static final String ERROR_ID = "err.user.invalid_identifier"; + + private final List givenUsernames; + private final List invalidUsernames; + + public InvalidUsernamesException(List givenUsernames, List invalidUsernames) { + this.givenUsernames = givenUsernames; + this.invalidUsernames = invalidUsernames; + } + + public InvalidUsernamesException(ErrorResponse errorResponse) { + this(Arrays.stream(errorResponse.details().get("givenUsernames").split(",")).toList(), + Arrays.stream(errorResponse.details().get("invalidUsernames").split(",")).toList()); + } + + @Override + public String getMessage() { + return "Users with invalid identifiers were found"; + } + + public List getGivenUsernames() { + return givenUsernames; + } + + public List getInvalidUsernames() { + return invalidUsernames; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + Function, String> toString = input -> String.join(",", input); + + details.put("givenUsernames", toString.apply(getGivenUsernames())); + details.put("invalidUsernames", toString.apply(getInvalidUsernames())); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/NoRoomsAvailableException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/NoRoomsAvailableException.java new file mode 100644 index 0000000..1b9b79a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/NoRoomsAvailableException.java @@ -0,0 +1,39 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class NoRoomsAvailableException extends ApiException { + + public static final String ERROR_ID = "err.room.no_available"; + + private final String nodeId; + + public NoRoomsAvailableException(String nodeId) { + this.nodeId = nodeId; + } + + public NoRoomsAvailableException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId")); + } + + @Override + public String getMessage() { + return "There are no rooms available in node '" + nodeId + "'"; + } + + public String getNodeId() { + return nodeId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("nodeId", getNodeId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/NodeAlreadyExistException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/NodeAlreadyExistException.java new file mode 100644 index 0000000..b83d11c --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/NodeAlreadyExistException.java @@ -0,0 +1,39 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class NodeAlreadyExistException extends ApiException { + + public static final String ERROR_ID = "err.node.already_exists"; + + private final String nodeId; + + public NodeAlreadyExistException(String nodeId) { + this.nodeId = nodeId; + } + + public NodeAlreadyExistException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId")); + } + + @Override + public String getMessage() { + return "Node with identifier '" + nodeId + "' is already exists"; + } + + public String getNodeId() { + return nodeId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("nodeId", getNodeId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/NodeNotFoundException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/NodeNotFoundException.java new file mode 100644 index 0000000..992a57e --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/NodeNotFoundException.java @@ -0,0 +1,39 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class NodeNotFoundException extends ApiException { + + public static final String ERROR_ID = "err.node.not_found"; + + private final String nodeId; + + public NodeNotFoundException(String nodeId) { + this.nodeId = nodeId; + } + + public NodeNotFoundException(ErrorResponse errorResponse) { + this(errorResponse.details().get("node")); + } + + @Override + public String getMessage() { + return "Node '" + nodeId + "' does not found"; + } + + public String getNodeId() { + return nodeId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("node", getNodeId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAlreadyExistException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAlreadyExistException.java new file mode 100644 index 0000000..a18c93f --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAlreadyExistException.java @@ -0,0 +1,47 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class RoomAlreadyExistException extends ApiException { + + public static final String ERROR_ID = "err.room.already_exists"; + + private final String nodeId; + private final String roomId; + + public RoomAlreadyExistException(String nodeId, String roomId) { + this.nodeId = nodeId; + this.roomId = roomId; + } + + public RoomAlreadyExistException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId"), + errorResponse.details().get("roomId")); + } + + @Override + public String getMessage() { + return "Room with identifier '" + roomId + "' in node '" + nodeId + "' is already exists"; + } + + public String getNodeId() { + return nodeId; + } + + public String getRoomId() { + return roomId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("nodeId", getNodeId()); + details.put("roomId", getRoomId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAreFullException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAreFullException.java new file mode 100644 index 0000000..ffd9fd5 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomAreFullException.java @@ -0,0 +1,47 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class RoomAreFullException extends ApiException { + + public static final String ERROR_ID = "err.room.are_full"; + + private final String nodeId; + private final String roomId; + + public RoomAreFullException(String nodeId, String roomId) { + this.nodeId = nodeId; + this.roomId = roomId; + } + + public RoomAreFullException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId"), + errorResponse.details().get("roomId")); + } + + @Override + public String getMessage() { + return "Room with identifier '" + roomId + "' in node '" + nodeId + "' are full"; + } + + public String getNodeId() { + return nodeId; + } + + public String getRoomId() { + return roomId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("nodeId", getNodeId()); + details.put("roomId", getRoomId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/exception/RoomNotFoundException.java b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomNotFoundException.java new file mode 100644 index 0000000..01ae57a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/exception/RoomNotFoundException.java @@ -0,0 +1,47 @@ +package ru.dragonestia.picker.api.exception; + +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.Map; + +public final class RoomNotFoundException extends ApiException { + + public static final String ERROR_ID = "err.room.not_found"; + + private final String nodeId; + private final String roomId; + + public RoomNotFoundException(String nodeId, String roomId) { + this.nodeId = nodeId; + this.roomId = roomId; + } + + public RoomNotFoundException(ErrorResponse errorResponse) { + this(errorResponse.details().get("nodeId"), + errorResponse.details().get("roomId")); + } + + @Override + public String getMessage() { + return "Room '" + roomId + "' in node '" + nodeId + "' does not found"; + } + + public String getNodeId() { + return nodeId; + } + + public String getRoomId() { + return roomId; + } + + @Override + public String getErrorId() { + return ERROR_ID; + } + + @Override + public void appendDetailsToErrorResponse(Map details) { + details.put("node", getNodeId()); + details.put("room", getRoomId()); + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/model/Node.java b/api/src/main/java/ru/dragonestia/picker/api/model/Node.java new file mode 100644 index 0000000..1573e5e --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/model/Node.java @@ -0,0 +1,39 @@ +package ru.dragonestia.picker.api.model; + +import ru.dragonestia.picker.api.model.type.PickingMode; + +public class Node { + + private String id; + private PickingMode mode; + + private Node() {} + + public Node(String id, PickingMode mode) { + this.id = id; + this.mode = mode; + } + + public String getId() { + return id; + } + + public PickingMode getMode() { + return mode; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (object instanceof Node other) { + return id.equals(other.id); + } + return false; + } +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/model/Room.java b/api/src/main/java/ru/dragonestia/picker/api/model/Room.java new file mode 100644 index 0000000..6415fd3 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/model/Room.java @@ -0,0 +1,73 @@ +package ru.dragonestia.picker.api.model; + +import java.beans.Transient; + +public class Room { + + public final static int INFINITE_SLOTS = -1; + + private String id; + private String nodeId; + private int slots; + private String payload; + private boolean locked = false; + + private Room() {} + + public Room(String id, String nodeId, int slots, String payload) { + this.id = id; + this.nodeId = nodeId; + this.slots = slots; + this.payload = payload; + } + + public Room(String id, Node node, int limit, String payload) { + this(id, node.getId(), limit, payload); + } + + public String getId() { + return id; + } + + public String getNodeId() { + return nodeId; + } + + public int getSlots() { + return slots; + } + + public String getPayload() { + return payload; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean value) { + locked = value; + } + + @Transient + public boolean isUnlimited() { + return slots == INFINITE_SLOTS; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (object instanceof Room other) { + return id.equals(other.id); + } + return false; + } + + public record Short(String id, int slots, boolean locked) {} +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/User.java b/api/src/main/java/ru/dragonestia/picker/api/model/User.java similarity index 61% rename from control-panel/src/main/java/ru/dragonestia/picker/cp/model/User.java rename to api/src/main/java/ru/dragonestia/picker/api/model/User.java index a91553a..2ae7a45 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/User.java +++ b/api/src/main/java/ru/dragonestia/picker/api/model/User.java @@ -1,8 +1,18 @@ -package ru.dragonestia.picker.cp.model; +package ru.dragonestia.picker.api.model; -import lombok.NonNull; +public class User { -public record User(@NonNull String id) { + private String id; + + private User() {} + + public User(String id) { + this.id = id; + } + + public String getId() { + return id; + } @Override public int hashCode() { diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/PickingMode.java b/api/src/main/java/ru/dragonestia/picker/api/model/type/PickingMode.java similarity index 51% rename from control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/PickingMode.java rename to api/src/main/java/ru/dragonestia/picker/api/model/type/PickingMode.java index f48ec09..9e3c792 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/PickingMode.java +++ b/api/src/main/java/ru/dragonestia/picker/api/model/type/PickingMode.java @@ -1,14 +1,17 @@ -package ru.dragonestia.picker.cp.model.type; +package ru.dragonestia.picker.api.model.type; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor public enum PickingMode { SEQUENTIAL_FILLING("Sequential filling"), ROUND_ROBIN("Round Robin"), LEAST_PICKED("Least Picked"); private final String name; + + PickingMode(String name) { + this.name = name; + } + + public String getName() { + return name; + } } diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/NodeRepository.java b/api/src/main/java/ru/dragonestia/picker/api/repository/NodeRepository.java new file mode 100644 index 0000000..5609f5c --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/NodeRepository.java @@ -0,0 +1,19 @@ +package ru.dragonestia.picker.api.repository; + +import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; +import ru.dragonestia.picker.api.model.Node; + +import java.util.List; +import java.util.Optional; + +public interface NodeRepository { + + void register(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException; + + List all(); + + Optional find(String nodeId); + + void remove(String nodeId); +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/RoomRepository.java b/api/src/main/java/ru/dragonestia/picker/api/repository/RoomRepository.java new file mode 100644 index 0000000..c6b3239 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/RoomRepository.java @@ -0,0 +1,23 @@ +package ru.dragonestia.picker.api.repository; + +import ru.dragonestia.picker.api.exception.*; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.model.Room; + +import java.util.List; +import java.util.Optional; + +public interface RoomRepository { + + void register(Room room) throws NodeNotFoundException, InvalidRoomIdentifierException, RoomAlreadyExistException; + + void remove(Room room) throws NodeNotFoundException; + + void remove(Node node, Room.Short room) throws NodeNotFoundException; + + List all(Node node) throws NodeNotFoundException; + + Optional find(Node node, String roomId) throws NodeNotFoundException; + + void lock(Room room, boolean value) throws NodeNotFoundException, RoomNotFoundException; +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java b/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java new file mode 100644 index 0000000..1cffb9f --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/UserRepository.java @@ -0,0 +1,19 @@ +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.model.Room; +import ru.dragonestia.picker.api.model.User; + +import java.util.Collection; +import java.util.List; + +public interface UserRepository { + + void linkWithRoom(Room room, Collection users, boolean force) throws NodeNotFoundException, RoomNotFoundException, RoomAreFullException; + + void unlinkFromRoom(Room room, Collection users) throws NodeNotFoundException, RoomNotFoundException; + + List all(Room room) throws NodeNotFoundException, RoomNotFoundException; +} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/ErrorResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/ErrorResponse.java new file mode 100644 index 0000000..7b2b5e5 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/ErrorResponse.java @@ -0,0 +1,5 @@ +package ru.dragonestia.picker.api.repository.response; + +import java.util.Map; + +public record ErrorResponse(String errorId, String message, Map details) {} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/LinkUsersWithRoomResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/LinkUsersWithRoomResponse.java new file mode 100644 index 0000000..b2278b2 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/LinkUsersWithRoomResponse.java @@ -0,0 +1,3 @@ +package ru.dragonestia.picker.api.repository.response; + +public record LinkUsersWithRoomResponse(int usedSlots, int totalSlots) {} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeDetailsResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeDetailsResponse.java new file mode 100644 index 0000000..bc74a49 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeDetailsResponse.java @@ -0,0 +1,5 @@ +package ru.dragonestia.picker.api.repository.response; + +import ru.dragonestia.picker.api.model.Node; + +public record NodeDetailsResponse(Node node) {} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeListResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeListResponse.java new file mode 100644 index 0000000..c36f5a6 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/NodeListResponse.java @@ -0,0 +1,7 @@ +package ru.dragonestia.picker.api.repository.response; + +import ru.dragonestia.picker.api.model.Node; + +import java.util.List; + +public record NodeListResponse(List nodes) {} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomInfoResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomInfoResponse.java new file mode 100644 index 0000000..4b676d5 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomInfoResponse.java @@ -0,0 +1,5 @@ +package ru.dragonestia.picker.api.repository.response; + +import ru.dragonestia.picker.api.model.Room; + +public record RoomInfoResponse(Room room) {} diff --git a/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomListResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomListResponse.java new file mode 100644 index 0000000..de83970 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomListResponse.java @@ -0,0 +1,7 @@ +package ru.dragonestia.picker.api.repository.response; + +import ru.dragonestia.picker.api.model.Room; + +import java.util.List; + +public record RoomListResponse(String node, List rooms) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomUserListResponse.java b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomUserListResponse.java similarity index 51% rename from app/src/main/java/ru/dragonestia/picker/controller/response/RoomUserListResponse.java rename to api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomUserListResponse.java index 9d9ea0c..6966788 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomUserListResponse.java +++ b/api/src/main/java/ru/dragonestia/picker/api/repository/response/RoomUserListResponse.java @@ -1,6 +1,6 @@ -package ru.dragonestia.picker.controller.response; +package ru.dragonestia.picker.api.repository.response; -import ru.dragonestia.picker.model.User; +import ru.dragonestia.picker.api.model.User; import java.util.List; diff --git a/api/src/main/java/ru/dragonestia/picker/api/utils/ValidateIdentifier.java b/api/src/main/java/ru/dragonestia/picker/api/utils/ValidateIdentifier.java new file mode 100644 index 0000000..5363618 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/picker/api/utils/ValidateIdentifier.java @@ -0,0 +1,18 @@ +package ru.dragonestia.picker.api.utils; + +public class ValidateIdentifier { + + private ValidateIdentifier() {} + + public static boolean forNode(String nodeId) { + return nodeId.matches("^[a-z\\d-]+$"); + } + + public static boolean forRoom(String roomId) { + return roomId.matches("^[a-z\\d-]+$"); + } + + public static boolean forUser(String username) { + return username.matches("^[aA-zZ\\d-.\\s:@_;]+$"); + } +} diff --git a/app/build.gradle b/app/build.gradle index 7665c1a..09a0c60 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,7 @@ configurations { } dependencies { + implementation project(":api") implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java b/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java index 1daf508..b2362c8 100644 --- a/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java +++ b/app/src/main/java/ru/dragonestia/picker/config/TestConfig.java @@ -7,11 +7,11 @@ import org.springframework.context.annotation.Profile; import org.springframework.lang.NonNull; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.interceptor.DebugInterceptor; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.model.type.SlotLimit; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.NodeRepository; diff --git a/app/src/main/java/ru/dragonestia/picker/controller/ExceptionHandlerController.java b/app/src/main/java/ru/dragonestia/picker/controller/ExceptionHandlerController.java new file mode 100644 index 0000000..98615ce --- /dev/null +++ b/app/src/main/java/ru/dragonestia/picker/controller/ExceptionHandlerController.java @@ -0,0 +1,65 @@ +package ru.dragonestia.picker.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import ru.dragonestia.picker.api.exception.*; +import ru.dragonestia.picker.api.repository.response.ErrorResponse; + +import java.util.HashMap; + +@RestControllerAdvice +public class ExceptionHandlerController { + + @ExceptionHandler(NodeNotFoundException.class) + ResponseEntity nodeNotFound(NodeNotFoundException ex) { + return create(404, ex); + } + + @ExceptionHandler(RoomNotFoundException.class) + ResponseEntity roomNotFound(RoomNotFoundException ex) { + return create(404, ex); + } + + @ExceptionHandler(InvalidUsernamesException.class) + ResponseEntity invalidUsernames(InvalidUsernamesException ex) { + return create(400, ex); + } + + @ExceptionHandler(InvalidNodeIdentifierException.class) + ResponseEntity invalidNodeIdentifier(InvalidNodeIdentifierException ex) { + return create(400, ex); + } + + @ExceptionHandler(InvalidRoomIdentifierException.class) + ResponseEntity invalidRoomIdentifier(InvalidRoomIdentifierException ex) { + return create(400, ex); + } + + @ExceptionHandler(NodeAlreadyExistException.class) + ResponseEntity nodeAlreadyExists(NodeAlreadyExistException ex) { + return create(400, ex); + } + + @ExceptionHandler(RoomAlreadyExistException.class) + ResponseEntity roomAlreadyExists(RoomAlreadyExistException ex) { + return create(400, ex); + } + + @ExceptionHandler(RoomAreFullException.class) + ResponseEntity roomAreFull(RoomAreFullException ex) { + return create(400, ex); + } + + @ExceptionHandler(NoRoomsAvailableException.class) + ResponseEntity noRoomsAvailable(NoRoomsAvailableException ex) { + return create(400, ex); + } + + private ResponseEntity create(int code, ApiException ex) { + var details = new HashMap(); + ex.appendDetailsToErrorResponse(details); + + return ResponseEntity.status(code).body(new ErrorResponse(ex.getErrorId(), ex.getMessage(), details)); + } +} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java b/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java index fa3a4df..8d7b4db 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java +++ b/app/src/main/java/ru/dragonestia/picker/controller/NodeController.java @@ -1,22 +1,18 @@ package ru.dragonestia.picker.controller; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.dragonestia.picker.controller.response.NodeDetailsResponse; -import ru.dragonestia.picker.controller.response.NodeListResponse; -import ru.dragonestia.picker.controller.response.NodeRegisterResponse; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; +import ru.dragonestia.picker.api.model.type.PickingMode; +import ru.dragonestia.picker.api.repository.response.NodeDetailsResponse; +import ru.dragonestia.picker.api.repository.response.NodeListResponse; import ru.dragonestia.picker.model.Node; -import ru.dragonestia.picker.model.Room; -import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.util.NamingValidator; -import java.util.LinkedList; -import java.util.Optional; +import java.util.Arrays; @RestController @RequestMapping("/nodes") @@ -25,47 +21,35 @@ public class NodeController { private final NodeService nodeService; private final RoomService roomService; + private final NamingValidator namingValidator; @GetMapping NodeListResponse allNodes() { - return new NodeListResponse(nodeService.all()); + return new NodeListResponse(nodeService.all().stream().map(Node::toResponseObject).toList()); } @PostMapping - NodeRegisterResponse registerNode(@RequestParam(name = "nodeId") String nodeId, + ResponseEntity registerNode(@RequestParam(name = "nodeId") String nodeId, @RequestParam(name = "method") PickingMode method) { - try { - nodeService.create(new Node(nodeId, method)); - } catch (IllegalArgumentException ex) { - return new NodeRegisterResponse(false, ex.getMessage()); - } catch (Error error) { - new NodeRegisterResponse(false, error.getMessage()); - } - - return new NodeRegisterResponse(true, ""); + nodeService.create(new Node(nodeId, method)); + return ResponseEntity.ok().build(); } @GetMapping("/{nodeId}") ResponseEntity nodeDetails(@PathVariable("nodeId") String nodeId) { - if (!NamingValidator.validateNodeId(nodeId)) { - return new ResponseEntity<>(HttpStatusCode.valueOf(404)); - } + namingValidator.validateNodeId(nodeId); - var nodeOpt = nodeService.find(nodeId); - return nodeOpt.map(node -> ResponseEntity.ok(new NodeDetailsResponse(node))) - .orElseGet(() -> new ResponseEntity<>(HttpStatusCode.valueOf(404))); + return nodeService.find(nodeId) + .map(node -> ResponseEntity.ok(new NodeDetailsResponse(node.toResponseObject()))) + .orElseThrow(() -> new NodeNotFoundException(nodeId)); } @DeleteMapping("/{nodeId}") ResponseEntity removeNode(@PathVariable("nodeId") String nodeId) { - if (!NamingValidator.validateNodeId(nodeId)) { - return ResponseEntity.ok().build(); - } - - var nodeOpt = nodeService.find(nodeId); - nodeOpt.ifPresent(nodeService::remove); + namingValidator.validateNodeId(nodeId); + nodeService.find(nodeId).ifPresent(nodeService::remove); return ResponseEntity.ok().build(); } @@ -73,30 +57,11 @@ public class NodeController { ResponseEntity pickRoom(@PathVariable("nodeId") String nodeId, @RequestParam(name = "userIds") String userIds) { - if (!NamingValidator.validateNodeId(nodeId)) { - return new ResponseEntity<>(HttpStatusCode.valueOf(404)); - } + namingValidator.validateNodeId(nodeId); - var nodeOpt = nodeService.find(nodeId); - if (nodeOpt.isEmpty()) { - return new ResponseEntity<>(HttpStatusCode.valueOf(404)); - } - - var node = nodeOpt.get(); - - var list = new LinkedList(); - for (var username: userIds.split(",")) { // TODO: create warnings about invalid usernames - if (!NamingValidator.validateUserId(username)) continue; - - list.add(new User(username)); - } - - Room room; - try { - room = roomService.pickAvailable(node, list); - } catch (RuntimeException ex) { - return new ResponseEntity<>(HttpStatusCode.valueOf(409)); - } + var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + var room = roomService.pickAvailable(node, users); return ResponseEntity.ok(room); // TODO: make other json schema } diff --git a/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java b/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java index d915989..d2eea4f 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java +++ b/app/src/main/java/ru/dragonestia/picker/controller/RoomController.java @@ -4,17 +4,16 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.dragonestia.picker.controller.response.RoomInfoResponse; -import ru.dragonestia.picker.controller.response.RoomListResponse; -import ru.dragonestia.picker.controller.response.RoomRegisterResponse; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; +import ru.dragonestia.picker.api.exception.RoomNotFoundException; +import ru.dragonestia.picker.api.repository.response.RoomInfoResponse; +import ru.dragonestia.picker.api.repository.response.RoomListResponse; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.type.SlotLimit; import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.util.NamingValidator; -import java.util.Objects; - @Log4j2 @RestController @RequestMapping("/nodes/{nodeId}/rooms") @@ -23,49 +22,40 @@ public class RoomController { private final NodeService nodeService; private final RoomService roomService; + private final NamingValidator namingValidator; @GetMapping ResponseEntity all(@PathVariable(name = "nodeId") String nodeId) { - var nodeOpt = nodeService.find(nodeId); - return nodeOpt.map(node -> ResponseEntity.ok(new RoomListResponse(nodeId, - roomService.all(node).stream() - .map(room -> new RoomListResponse.RoomDTO(room.getId(), room.getSlots().getSlots(), room.isLocked())) - .toList() - ))).orElseGet(() -> ResponseEntity.notFound().build()); + + return nodeService.find(nodeId) + .map(node -> ResponseEntity.ok(new RoomListResponse(nodeId, + roomService.all(node).stream() + .map(room -> new ru.dragonestia.picker.api.model.Room.Short(room.getId(), room.getSlots().getSlots(), room.isLocked())) + .toList() + ))).orElseThrow(() -> new NodeNotFoundException(nodeId)); } @PostMapping - ResponseEntity register(@PathVariable(name = "nodeId") String nodeId, - @RequestParam(name = "roomId") String roomId, - @RequestParam(name = "slots") int slots, - @RequestParam(name = "payload") String payload, - @RequestParam(name = "locked", defaultValue = "false") boolean locked) { + ResponseEntity register(@PathVariable(name = "nodeId") String nodeId, + @RequestParam(name = "roomId") String roomId, + @RequestParam(name = "slots") int slots, + @RequestParam(name = "payload") String payload, + @RequestParam(name = "locked", defaultValue = "false") boolean locked) { - var nodeOpt = nodeService.find(nodeId); - - if (nodeOpt.isEmpty()) { - return ResponseEntity.status(404) - .body(new RoomRegisterResponse(false, "Node does not exist")); - } - - var room = Room.create(roomId, nodeOpt.get(), SlotLimit.of(slots), payload); + var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + var room = Room.create(roomId, node, SlotLimit.of(slots), payload); room.setLocked(locked); - try { - roomService.create(room); - return ResponseEntity.ok(new RoomRegisterResponse(true, "")); - } catch (Error error) { - return ResponseEntity.status(400).body(new RoomRegisterResponse(false, error.getMessage())); - } catch (Exception ex) { - return ResponseEntity.status(500).body(new RoomRegisterResponse(false, ex.getMessage())); - } + roomService.create(room); + + return ResponseEntity.ok().build(); } @DeleteMapping("/{roomId}") ResponseEntity remove(@PathVariable("nodeId") String nodeId, @PathVariable("roomId") String roomId) { - if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) { - return ResponseEntity.ok().build(); - } + + namingValidator.validateNodeId(nodeId); + namingValidator.validateRoomId(nodeId, roomId); var nodeOpt = nodeService.find(nodeId); nodeOpt.flatMap(node -> roomService.find(node, roomId)) @@ -77,40 +67,26 @@ public class RoomController { @GetMapping("/{roomId}") ResponseEntity info(@PathVariable("nodeId") String nodeId, @PathVariable("roomId") String roomId) { - if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) { - return ResponseEntity.ok().build(); - } - var nodeOpt = nodeService.find(nodeId); - if (nodeOpt.isEmpty()) { - return ResponseEntity.notFound().build(); - } + namingValidator.validateNodeId(nodeId); + namingValidator.validateRoomId(nodeId, roomId); - var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId); - return roomOpt.map(room -> ResponseEntity.ok(new RoomInfoResponse(room))) - .orElseGet(() -> ResponseEntity.notFound().build()); + var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + return roomService.find(node, roomId) + .map(room -> ResponseEntity.ok(new RoomInfoResponse(room.toResponseObject()))) + .orElseThrow(() -> new RoomNotFoundException(nodeId, roomId)); } - @PostMapping("/{roomId}/lock") + @PutMapping("/{roomId}/lock") ResponseEntity lockBucket(@PathVariable("nodeId") String nodeId, - @PathVariable("roomId") String roomId, - @RequestParam(name = "newState") boolean value) { + @PathVariable("roomId") String roomId, + @RequestParam(name = "newState") boolean value) { - if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) { - return ResponseEntity.notFound().build(); - } + namingValidator.validateNodeId(nodeId); + namingValidator.validateRoomId(nodeId, roomId); - var nodeOpt = nodeService.find(nodeId); - if (nodeOpt.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId); - if (roomOpt.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - var room = roomOpt.get(); + var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId)); room.setLocked(value); return ResponseEntity.ok(true); } diff --git a/app/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java b/app/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java index 55c9b9d..fbd57d1 100644 --- a/app/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java +++ b/app/src/main/java/ru/dragonestia/picker/controller/UserRoomController.java @@ -3,8 +3,10 @@ package ru.dragonestia.picker.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import ru.dragonestia.picker.controller.response.RoomUserListResponse; -import ru.dragonestia.picker.controller.response.LinkUsersWithRoomResponse; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; +import ru.dragonestia.picker.api.exception.RoomNotFoundException; +import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse; +import ru.dragonestia.picker.api.repository.response.RoomUserListResponse; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; @@ -13,8 +15,7 @@ import ru.dragonestia.picker.service.NodeService; import ru.dragonestia.picker.service.UserService; import ru.dragonestia.picker.util.NamingValidator; -import java.util.LinkedList; -import java.util.Objects; +import java.util.Arrays; @RequiredArgsConstructor @RestController @@ -24,21 +25,15 @@ public class UserRoomController { private final NodeService nodeService; private final RoomService roomService; private final UserService userService; + private final NamingValidator namingValidator; @GetMapping ResponseEntity usersInsideRoom(@PathVariable(name = "nodeId") String nodeId, - @PathVariable(name = "roomId") String bucketId) { - - Room room; - try { - var temp = getNodeAndRoom(nodeId, bucketId); - room = temp.room(); - } catch (Error error) { - return ResponseEntity.notFound().build(); - } + @PathVariable(name = "roomId") String roomId) { + var room = getNodeAndRoom(nodeId, roomId).room(); var users = userService.getRoomUsers(room); - return ResponseEntity.ok(new RoomUserListResponse(room.getSlots().getSlots(), users.size(), users)); + return ResponseEntity.ok(new RoomUserListResponse(room.getSlots().getSlots(), users.size(), users.stream().map(User::toResponseObject).toList())); } @PostMapping @@ -47,28 +42,10 @@ public class UserRoomController { @RequestParam(name = "userIds") String userIds, @RequestParam(name = "force") boolean force) { - Room room; - try { - var temp = getNodeAndRoom(nodeId, roomId); - room = temp.room(); - } catch (Error error) { - return ResponseEntity.status(404).body(new LinkUsersWithRoomResponse(false, error.getMessage(), -1, -1)); - } - - var list = new LinkedList(); - for (var username: userIds.split(",")) { // TODO: create warnings about invalid usernames - if (!NamingValidator.validateUserId(username)) continue; - - list.add(new User(username)); - } - - try { - int usedSlots = userService.linkUsersWithRoom(room, list, force); - - return ResponseEntity.ok(new LinkUsersWithRoomResponse(true, "Success", usedSlots, room.getSlots().getSlots())); - } catch (Error error) { - return ResponseEntity.status(400).body(new LinkUsersWithRoomResponse(false, error.getMessage(), -1, -1)); - } + var room = getNodeAndRoom(nodeId, roomId).room(); + var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + var usedSlots = userService.linkUsersWithRoom(room, users, force); + return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getSlots().getSlots())); } @DeleteMapping @@ -76,43 +53,21 @@ public class UserRoomController { @PathVariable(name = "roomId") String roomId, @RequestParam(name = "userIds") String userIds) { - Room room; - try { - var temp = getNodeAndRoom(nodeId, roomId); - room = temp.room(); - - var list = new LinkedList(); - for (var username: userIds.split(",")) { // TODO: create warnings about invalid usernames - if (!NamingValidator.validateUserId(username)) continue; - - list.add(new User(username)); - } - - userService.unlinkUsersFromRoom(room, list); - } catch (Error error) { - return ResponseEntity.notFound().build(); - } - + var room = getNodeAndRoom(nodeId, roomId).room(); + var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList()); + userService.unlinkUsersFromRoom(room, users); return ResponseEntity.ok().build(); } private record NodeAndRoom(Node node, Room room) {} private NodeAndRoom getNodeAndRoom(String nodeId, String roomId) { - if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) { - throw new Error(); - } + namingValidator.validateNodeId(nodeId); + namingValidator.validateRoomId(nodeId, roomId); - var nodeOpt = nodeService.find(nodeId); - if (nodeOpt.isEmpty()) { - throw new Error(); - } + var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId)); - var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId); - if (roomOpt.isEmpty()) { - throw new Error(); - } - - return new NodeAndRoom(nodeOpt.get(), roomOpt.get()); + return new NodeAndRoom(node, room); } } diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/LinkUsersWithRoomResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/LinkUsersWithRoomResponse.java deleted file mode 100644 index 0508393..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/LinkUsersWithRoomResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -public record LinkUsersWithRoomResponse(boolean success, String message, int usedSlots, int totalSlots) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeDetailsResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/NodeDetailsResponse.java deleted file mode 100644 index 8be98b2..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeDetailsResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -import ru.dragonestia.picker.model.Node; - -public record NodeDetailsResponse(Node node) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeListResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/NodeListResponse.java deleted file mode 100644 index f85b04c..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeListResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -import ru.dragonestia.picker.model.Node; - -import java.util.List; - -public record NodeListResponse(List nodes) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeRegisterResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/NodeRegisterResponse.java deleted file mode 100644 index 3f30dd6..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/NodeRegisterResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -public record NodeRegisterResponse(boolean success, String message) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomInfoResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/RoomInfoResponse.java deleted file mode 100644 index d432406..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomInfoResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -import ru.dragonestia.picker.model.Room; - -public record RoomInfoResponse(Room room) {} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomListResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/RoomListResponse.java deleted file mode 100644 index 9e66507..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomListResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -import java.util.List; - -public record RoomListResponse(String node, List rooms) { - - public record RoomDTO(String id, int slots, boolean locked) {} -} diff --git a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomRegisterResponse.java b/app/src/main/java/ru/dragonestia/picker/controller/response/RoomRegisterResponse.java deleted file mode 100644 index 21893c6..0000000 --- a/app/src/main/java/ru/dragonestia/picker/controller/response/RoomRegisterResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.controller.response; - -public record RoomRegisterResponse(boolean success, String message) {} diff --git a/app/src/main/java/ru/dragonestia/picker/model/Node.java b/app/src/main/java/ru/dragonestia/picker/model/Node.java index ee9b88e..300245e 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/Node.java +++ b/app/src/main/java/ru/dragonestia/picker/model/Node.java @@ -1,7 +1,7 @@ package ru.dragonestia.picker.model; import lombok.NonNull; -import ru.dragonestia.picker.model.type.PickingMode; +import ru.dragonestia.picker.api.model.type.PickingMode; public record Node(@NonNull String id, @NonNull PickingMode mode) { @@ -19,4 +19,8 @@ public record Node(@NonNull String id, @NonNull PickingMode mode) { } return false; } + + public ru.dragonestia.picker.api.model.Node toResponseObject() { + return new ru.dragonestia.picker.api.model.Node(id, mode); + } } diff --git a/app/src/main/java/ru/dragonestia/picker/model/Room.java b/app/src/main/java/ru/dragonestia/picker/model/Room.java index ae18b08..4dc7d31 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/Room.java +++ b/app/src/main/java/ru/dragonestia/picker/model/Room.java @@ -43,4 +43,10 @@ public class Room { } return false; } + + public ru.dragonestia.picker.api.model.Room toResponseObject() { + var result = new ru.dragonestia.picker.api.model.Room(id, nodeId, slots.getSlots(), payload); + result.setLocked(locked); + return result; + } } diff --git a/app/src/main/java/ru/dragonestia/picker/model/User.java b/app/src/main/java/ru/dragonestia/picker/model/User.java index 79bbdca..fb9a226 100644 --- a/app/src/main/java/ru/dragonestia/picker/model/User.java +++ b/app/src/main/java/ru/dragonestia/picker/model/User.java @@ -18,4 +18,8 @@ public record User(@NonNull String id) { } return false; } + + public ru.dragonestia.picker.api.model.User toResponseObject() { + return new ru.dragonestia.picker.api.model.User(id); + } } diff --git a/app/src/main/java/ru/dragonestia/picker/model/type/PickingMode.java b/app/src/main/java/ru/dragonestia/picker/model/type/PickingMode.java deleted file mode 100644 index 9038526..0000000 --- a/app/src/main/java/ru/dragonestia/picker/model/type/PickingMode.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.picker.model.type; - -public enum PickingMode { - SEQUENTIAL_FILLING, - ROUND_ROBIN, - LEAST_PICKED, -} diff --git a/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java index e22d7ff..61d0367 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/NodeRepository.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.repository; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; import java.util.List; @@ -7,7 +8,7 @@ import java.util.Optional; public interface NodeRepository { - void create(Node node); + void create(Node node) throws NodeAlreadyExistException; void delete(Node node); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java index 163d849..32de79b 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/RoomRepository.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.repository; +import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; @@ -10,7 +11,7 @@ import java.util.Optional; public interface RoomRepository { - void create(Room room); + void create(Room room) throws RoomAlreadyExistException; void remove(Room room); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java index 79fdd71..96cc56b 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/UserRepository.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.repository; +import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; @@ -9,7 +10,7 @@ import java.util.Map; public interface UserRepository { - Map linkWithRoom(Room room, Collection users, boolean force); + Map linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException; int unlinkWithRoom(Room room, Collection users); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java index 78cc845..e7b5f45 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/NodeRepositoryImpl.java @@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.NodeRepository; @@ -22,10 +23,10 @@ public class NodeRepositoryImpl implements NodeRepository { private final Map nodeMap = new ConcurrentHashMap<>(); @Override - public void create(Node node) { + public void create(Node node) throws NodeAlreadyExistException { synchronized (nodeMap) { if (nodeMap.containsKey(node.id())) { - throw new IllegalArgumentException("Node with id '" + node.id() + "' already exists"); + throw new NodeAlreadyExistException(node.id()); } nodeMap.put(node.id(), node); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java index 62aa45c..a2fe5a3 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/PickerRepository.java @@ -2,9 +2,9 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.picker.*; diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java index ea8d249..0267f7d 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/RoomRepositoryImpl.java @@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; @@ -21,7 +22,7 @@ public class RoomRepositoryImpl implements RoomRepository { private final Map node2roomsMap = new ConcurrentHashMap<>(); @Override - public void create(Room room) { + public void create(Room room) throws RoomAlreadyExistException { var nodeId = room.getNodeId(); synchronized (node2roomsMap) { @@ -35,7 +36,7 @@ public class RoomRepositoryImpl implements RoomRepository { var rooms = node2roomsMap.get(node.get()); if (rooms.containsKey(room.getId())) { - throw new IllegalArgumentException("Room already exists"); + throw new RoomAlreadyExistException(room.getNodeId(), room.getId()); } rooms.put(room.getId(), new RoomContainer(room, new AtomicInteger(0))); pickerRepository.find(room.getNodeId()).add(room); diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java index 1d35858..96977fc 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/UserRepositoryImpl.java @@ -2,10 +2,9 @@ package ru.dragonestia.picker.repository.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; -import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache; import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker; @@ -23,7 +22,7 @@ public class UserRepositoryImpl implements UserRepository { private final Map> roomUsers = new ConcurrentHashMap<>(); @Override - public Map linkWithRoom(Room room, Collection users, boolean force) { + public Map linkWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException { var result = new HashMap(); synchronized (usersMap) { @@ -39,7 +38,7 @@ public class UserRepositoryImpl implements UserRepository { } if (room.getSlots().getSlots() < usersSet.size() + users.size()) { - throw new Error("Room are full"); + throw new RoomAreFullException(room.getNodeId(), room.getId()); } } diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java index b5a73ee..d3dede4 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/LeastPickedPicker.java @@ -1,8 +1,8 @@ package ru.dragonestia.picker.repository.impl.picker; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java index 9367af8..14b9eb4 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoomPicker.java @@ -1,8 +1,8 @@ package ru.dragonestia.picker.repository.impl.picker; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; public interface RoomPicker extends Picker { diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java index 7a7180f..97067ba 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/RoundRobinPicker.java @@ -1,8 +1,8 @@ package ru.dragonestia.picker.repository.impl.picker; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.repository.UserRepository; import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList; diff --git a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java index db62015..dc416e5 100644 --- a/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java +++ b/app/src/main/java/ru/dragonestia/picker/repository/impl/picker/SequentialFillingPicker.java @@ -1,8 +1,8 @@ package ru.dragonestia.picker.repository.impl.picker; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.repository.UserRepository; import java.util.Collection; diff --git a/app/src/main/java/ru/dragonestia/picker/service/NodeService.java b/app/src/main/java/ru/dragonestia/picker/service/NodeService.java index 8333122..1df23f0 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/NodeService.java +++ b/app/src/main/java/ru/dragonestia/picker/service/NodeService.java @@ -1,5 +1,7 @@ package ru.dragonestia.picker.service; +import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; import java.util.List; @@ -7,7 +9,7 @@ import java.util.Optional; public interface NodeService { - void create(Node node); + void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException; void remove(Node node); diff --git a/app/src/main/java/ru/dragonestia/picker/service/RoomService.java b/app/src/main/java/ru/dragonestia/picker/service/RoomService.java index 27d965f..9aa24af 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/RoomService.java +++ b/app/src/main/java/ru/dragonestia/picker/service/RoomService.java @@ -1,5 +1,7 @@ package ru.dragonestia.picker.service; +import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException; +import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; @@ -9,7 +11,7 @@ import java.util.Optional; public interface RoomService { - void create(Room room); + void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException; void remove(Room room); diff --git a/app/src/main/java/ru/dragonestia/picker/service/UserService.java b/app/src/main/java/ru/dragonestia/picker/service/UserService.java index 682abb9..708e7a1 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/UserService.java +++ b/app/src/main/java/ru/dragonestia/picker/service/UserService.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.service; +import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; @@ -10,7 +11,7 @@ public interface UserService { List getUserRooms(User user); - int linkUsersWithRoom(Room room, Collection users, boolean force); + int linkUsersWithRoom(Room room, Collection users, boolean force) throws RoomAreFullException; void unlinkUsersFromRoom(Room room, Collection users); diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java b/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java index 304518d..7ec2ac8 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/NodeServiceImpl.java @@ -2,6 +2,8 @@ package ru.dragonestia.picker.service.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException; +import ru.dragonestia.picker.api.exception.NodeAlreadyExistException; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.service.NodeService; @@ -15,13 +17,11 @@ import java.util.Optional; public class NodeServiceImpl implements NodeService { private final NodeRepository nodeRepository; + private final NamingValidator namingValidator; @Override - public void create(Node node) { - if (!NamingValidator.validateNodeId(node.id())) { - throw new Error("Invalid node id format"); - } - + public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException { + namingValidator.validateNodeId(node.id()); nodeRepository.create(node); } diff --git a/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java b/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java index 76cbe50..1800cc3 100644 --- a/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java +++ b/app/src/main/java/ru/dragonestia/picker/service/impl/RoomServiceImpl.java @@ -2,6 +2,8 @@ package ru.dragonestia.picker.service.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException; +import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.User; @@ -17,13 +19,11 @@ import java.util.Optional; public class RoomServiceImpl implements RoomService { private final RoomRepository roomRepository; + private final NamingValidator namingValidator; @Override - public void create(Room room) { - if (!NamingValidator.validateRoomId(room.getId())) { - throw new Error("Invalid room id format"); - } - + public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException { + namingValidator.validateRoomId(room.getNodeId(), room.getId()); roomRepository.create(room); } diff --git a/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java b/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java index 1182f30..f51396b 100644 --- a/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java +++ b/app/src/main/java/ru/dragonestia/picker/util/NamingValidator.java @@ -1,19 +1,51 @@ package ru.dragonestia.picker.util; -import lombok.experimental.UtilityClass; +import org.springframework.stereotype.Component; +import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException; +import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException; +import ru.dragonestia.picker.api.exception.InvalidUsernamesException; +import ru.dragonestia.picker.api.utils.ValidateIdentifier; +import ru.dragonestia.picker.model.User; -@UtilityClass +import java.util.LinkedList; +import java.util.List; + +@Component public class NamingValidator { - public boolean validateNodeId(String input) { - return input.matches("^[a-z\\d-]+$"); + public void validateNodeId(String input) throws InvalidNodeIdentifierException { + if (ValidateIdentifier.forNode(input)) return; + + throw new InvalidNodeIdentifierException(input); } - public boolean validateRoomId(String input) { - return input.matches("^[a-z\\d-]+$"); + public void validateRoomId(String nodeId, String input) throws InvalidRoomIdentifierException { + if (ValidateIdentifier.forRoom(input)) return; + + throw new InvalidRoomIdentifierException(nodeId, input); } - public boolean validateUserId(String input) { - return input.matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$"); + private boolean validateUserId(String input) { + return ValidateIdentifier.forUser(input); + } + + public List validateUserIds(List input) throws InvalidUsernamesException { + var users = new LinkedList(); + var invalid = new LinkedList(); + + for (var username: input) { + if (validateUserId(username)) { + users.add(new User(username)); + continue; + } + + invalid.add(username); + } + + if (!invalid.isEmpty()) { + throw new InvalidUsernamesException(input, invalid); + } + + return users; } } diff --git a/app/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java b/app/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java index 7f5271c..d62d1da 100644 --- a/app/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java +++ b/app/src/test/java/ru/dragonestia/picker/config/FillingNodesConfig.java @@ -3,10 +3,10 @@ package ru.dragonestia.picker.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import ru.dragonestia.picker.api.model.type.PickingMode; import ru.dragonestia.picker.model.Node; import ru.dragonestia.picker.model.Room; import ru.dragonestia.picker.model.User; -import ru.dragonestia.picker.model.type.PickingMode; import ru.dragonestia.picker.model.type.SlotLimit; import ru.dragonestia.picker.repository.NodeRepository; import ru.dragonestia.picker.repository.RoomRepository; diff --git a/control-panel/build.gradle b/control-panel/build.gradle index db0a944..d16f44b 100644 --- a/control-panel/build.gradle +++ b/control-panel/build.gradle @@ -23,8 +23,11 @@ ext { } dependencies { + implementation project(":api") + implementation 'com.vaadin:vaadin-spring-boot-starter' compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AddUsers.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AddUsers.java index 6a12b1b..39addbc 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AddUsers.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/AddUsers.java @@ -9,18 +9,15 @@ import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import lombok.Getter; -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.User; +import ru.dragonestia.picker.api.model.Room; +import ru.dragonestia.picker.api.model.User; import java.util.Collection; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.Consumer; public class AddUsers extends Details { @@ -79,8 +76,7 @@ public class AddUsers extends Details { try { onCommit.accept(readAllUsers(), ignoreSlots.getValue()); } catch (Error error) { - Notification.show(error.getMessage(), 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error(error.getMessage()); } clear(); diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NavPath.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NavPath.java index 7b1bba1..f9af319 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NavPath.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NavPath.java @@ -10,7 +10,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; public class NavPath extends HorizontalLayout{ - public NavPath(Point root, Point... points) { + private NavPath(Point root, Point... points) { setWidth("100%"); setAlignItems(Alignment.CENTER); getStyle().set("background-color", "#F3F3F3") @@ -58,5 +58,20 @@ public class NavPath extends HorizontalLayout{ return button; } - public record Point(String name, String uri) {} + public static NavPath rootNodes() { + return new NavPath(new NavPath.Point("Nodes", "/nodes")); + } + + public static NavPath toNode(String nodeId) { + return new NavPath(new NavPath.Point("Nodes", "/nodes"), + new NavPath.Point(nodeId, "/nodes/" + nodeId)); + } + + public static NavPath toRoom(String nodeId, String roomId) { + return new NavPath(new NavPath.Point("Nodes", "/nodes"), + new NavPath.Point(nodeId, "/nodes/" + nodeId), + new NavPath.Point(roomId, "/nodes/" + nodeId + "/rooms/" + roomId)); + } + + private record Point(String name, String uri) {} } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NodeList.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NodeList.java index 0919c91..68d5eda 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NodeList.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/NodeList.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.cp.component; +import com.vaadin.flow.component.Html; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.dialog.Dialog; @@ -8,13 +9,11 @@ import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import lombok.Setter; -import ru.dragonestia.picker.cp.model.Node; +import ru.dragonestia.picker.api.model.Node; import java.util.List; import java.util.function.Consumer; @@ -51,14 +50,14 @@ public class NodeList extends VerticalLayout { var temp = input.trim(); nodesGrid.setItems(cachedNodes.stream() - .filter(node -> node.id().startsWith(temp)) + .filter(node -> node.getId().startsWith(temp)) .toList()); } private Grid createGrid() { var grid = new Grid<>(Node.class, false); - grid.addColumn(Node::id).setHeader("Identifier"); - grid.addColumn(node -> node.mode().getName()).setHeader("Mode"); + grid.addColumn(Node::getId).setHeader("Identifier"); + grid.addColumn(node -> node.getMode().getName()).setHeader("Mode"); grid.addComponentColumn(this::createManageButtons).setHeader("Manage"); return grid; } @@ -84,29 +83,28 @@ public class NodeList extends VerticalLayout { } private void clickDetailsButton(Node node) { - getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.id())); + getUI().ifPresent(ui -> ui.navigate("/nodes/" + node.getId())); } private void clickRemoveButton(Node node) { var dialog = new Dialog("Confirm node deletion"); - dialog.add(new Paragraph("Confirm that you want to delete node. Enter '" + node.id() + "' to field below and confirm.")); + dialog.add(new Html("

Confirm that you want to delete node. Enter " + node.getId() + " to field below and confirm.

")); var inputField = new TextField(); + inputField.setWidth("100%"); dialog.add(inputField); { // confirm var button = new Button("Confirm"); button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); button.addClickListener(event -> { - if (!node.id().equals(inputField.getValue())) { - Notification.show("Invalid input", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + if (!node.getId().equals(inputField.getValue())) { + Notifications.error("Invalid input"); return; } removeNode(node); - Notification.show("Node '" + node.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Node " + node.getId() + " was successfully removed!"); dialog.close(); }); @@ -129,7 +127,7 @@ public class NodeList extends VerticalLayout { private void removeNode(Node node) { if (removeMethod != null) { - removeMethod.accept(node.id()); + removeMethod.accept(node.getId()); } } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/Notifications.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/Notifications.java new file mode 100644 index 0000000..8a83c83 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/Notifications.java @@ -0,0 +1,53 @@ +package ru.dragonestia.picker.cp.component; + +import com.vaadin.flow.component.Html; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class Notifications { + + public Notification success(String text) { + var notification = create(VaadinIcon.CHECK_CIRCLE, text); + notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS); + notification.open(); + return notification; + } + + public Notification warn(String text) { + var notification = create(VaadinIcon.WARNING, text); + notification.addThemeVariants(NotificationVariant.LUMO_WARNING); + notification.open(); + return notification; + } + + public Notification error(String text) { + var notification = create(VaadinIcon.WARNING, text); + notification.addThemeVariants(NotificationVariant.LUMO_ERROR); + notification.open(); + return notification; + } + + private Notification create(VaadinIcon icon, String text) { + var layout = new HorizontalLayout(); + layout.add(new Icon(icon)); + layout.add(new Html("" + text + "")); + + var notification = new Notification(); + notification.setDuration(5000); + notification.setPosition(Notification.Position.TOP_END); + + var closeButton = new Button(VaadinIcon.CLOSE_SMALL.create(), event -> notification.close()); + closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE); + layout.add(closeButton); + + notification.add(layout); + return notification; + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterNode.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterNode.java index f12d9c2..ee5acf2 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterNode.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterNode.java @@ -7,8 +7,6 @@ import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.details.Details; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Span; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.radiobutton.RadioButtonGroup; import com.vaadin.flow.component.radiobutton.RadioGroupVariant; @@ -16,8 +14,8 @@ 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.cp.model.Node; -import ru.dragonestia.picker.cp.model.type.PickingMode; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.model.type.PickingMode; import java.util.function.Function; @@ -91,8 +89,7 @@ public class RegisterNode extends Details { error = "Invalid node id format"; } - Notification.show(error, 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error(error); return; } @@ -100,13 +97,11 @@ public class RegisterNode extends Details { var response = onSubmit.apply(node); clear(); if (response.error()) { - Notification.show(response.reason(), 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error(response.reason()); return; } - Notification.show("Node was successfully registered", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Node was successfully registered"); } public record Response(boolean error, @Nullable String reason) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterRoom.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterRoom.java index 7cae85c..7fcfd33 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterRoom.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RegisterRoom.java @@ -6,16 +6,13 @@ import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.checkbox.Checkbox; import com.vaadin.flow.component.details.Details; import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; 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.cp.model.Room; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.model.type.SlotLimit; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.model.Room; import java.util.function.Function; @@ -45,7 +42,7 @@ public class RegisterRoom extends Details { private TextField createNodeIdentifierField() { var field = new TextField("Node identifier"); field.setMinWidth(20, Unit.REM); - field.setValue(node.id()); + field.setValue(node.getId()); field.setReadOnly(true); return field; } @@ -106,23 +103,20 @@ public class RegisterRoom extends Details { error = "Invalid room id format"; } - Notification.show(error, 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error(error); return; } - var room = Room.create(nodeIdentifier, node, SlotLimit.unlimited(), payloadField.getValue()); + var room = new Room(nodeIdentifier, node, Room.INFINITE_SLOTS, payloadField.getValue()); room.setLocked(lockedField.getValue()); var response = onSubmit.apply(room); clear(); if (response.error()) { - Notification.show(response.reason(), 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error(response.reason()); return; } - Notification.show("Room was successfully registered", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Room was successfully registered"); } public record Response(boolean error, @Nullable String reason) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RoomList.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RoomList.java index 5d67551..576b10c 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RoomList.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/RoomList.java @@ -1,5 +1,6 @@ package ru.dragonestia.picker.cp.component; +import com.vaadin.flow.component.Html; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.dialog.Dialog; @@ -10,13 +11,11 @@ import com.vaadin.flow.component.html.Paragraph; 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.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import lombok.Setter; -import ru.dragonestia.picker.cp.model.dto.RoomDTO; +import ru.dragonestia.picker.api.model.Room; import java.util.List; import java.util.function.Consumer; @@ -24,12 +23,12 @@ import java.util.function.Consumer; public class RoomList extends VerticalLayout { private final String nodeIdentifier; - private final Grid roomsGrid; + private final Grid roomsGrid; private final TextField searchField; - private List cachedRooms; - @Setter private Consumer removeMethod; + private List cachedRooms; + @Setter private Consumer removeMethod; - public RoomList(String nodeIdentifier, List buckets) { + public RoomList(String nodeIdentifier, List buckets) { this.nodeIdentifier = nodeIdentifier; cachedRooms = buckets; @@ -57,9 +56,9 @@ public class RoomList extends VerticalLayout { .toList()); } - private Grid createGrid() { - var grid = new Grid<>(RoomDTO.class, false); - grid.addColumn(RoomDTO::id).setHeader("Identifier"); + private Grid createGrid() { + var grid = new Grid<>(Room.Short.class, false); + grid.addColumn(Room.Short::id).setHeader("Identifier"); grid.addComponentColumn(room -> { var result = new Span(); if (room.slots() == -1) { @@ -84,7 +83,7 @@ public class RoomList extends VerticalLayout { return grid; } - private HorizontalLayout createManageButtons(RoomDTO room) { + private HorizontalLayout createManageButtons(Room.Short room) { var layout = new HorizontalLayout(); { @@ -104,18 +103,19 @@ public class RoomList extends VerticalLayout { return layout; } - private void clickDetailsButton(RoomDTO bucket) { + private void clickDetailsButton(Room.Short bucket) { getUI().ifPresent(ui -> { ui.navigate("/nodes/" + nodeIdentifier + "/rooms/" + bucket.id()); }); } - private void clickRemoveButton(RoomDTO bucket) { + private void clickRemoveButton(Room.Short bucket) { var dialog = new Dialog("Confirm bucket deletion"); - dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.id() + "' to field below and confirm.")); + dialog.add(new Html("

Confirm that you want to delete bucket. Enter " + bucket.id() + " to field below and confirm.

")); var inputField = new TextField(); + inputField.setWidth("100%"); dialog.add(inputField); { // confirm @@ -123,14 +123,12 @@ public class RoomList extends VerticalLayout { button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); button.addClickListener(event -> { if (!bucket.id().equals(inputField.getValue())) { - Notification.show("Invalid input", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error("Invalid input"); return; } removeBucket(bucket); - Notification.show("Bucket '" + bucket.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Bucket " + bucket.id() + " was successfully removed!"); dialog.close(); }); @@ -146,12 +144,12 @@ public class RoomList extends VerticalLayout { dialog.open(); } - public void update(List buckets) { + public void update(List buckets) { cachedRooms = buckets; applySearch(searchField.getValue()); } - private void removeBucket(RoomDTO bucket) { + private void removeBucket(Room.Short bucket) { if (removeMethod != null) { removeMethod.accept(bucket); } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/UserList.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/UserList.java index adc5b55..8ee9eb8 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/component/UserList.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/component/UserList.java @@ -4,8 +4,8 @@ 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.orderedlayout.VerticalLayout; -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.User; +import ru.dragonestia.picker.api.model.Room; +import ru.dragonestia.picker.api.model.User; import java.util.ArrayList; import java.util.List; @@ -28,7 +28,7 @@ public class UserList extends VerticalLayout { private Grid createUsersGrid() { var grid = new Grid(); - grid.addColumn(User::id).setHeader("User Identifier").setFooter(totalUsers); + grid.addColumn(User::getId).setHeader("User Identifier").setFooter(totalUsers); grid.addColumn(user -> 0).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms") // TODO .setFooter(occupancy); grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO @@ -39,6 +39,12 @@ public class UserList extends VerticalLayout { cachedUsers = users; usersGrid.setItems(users); totalUsers.setText("Total users: " + users.size()); - occupancy.setText("Occupancy: %s".formatted(room.getUsingPercentage(users.size()))); + occupancy.setText("Occupancy: %s".formatted(getUsingPercentage(room, users.size()))); + } + + private String getUsingPercentage(Room room, int usedSlots) { + if (room.isUnlimited()) return "0%"; + double percent = usedSlots / (double) room.getSlots() * 100; + return ((int) percent) + "%"; } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/config/RestApiConfig.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/config/RestApiConfig.java index 51cf8ad..6e233ab 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/config/RestApiConfig.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/config/RestApiConfig.java @@ -17,7 +17,7 @@ public class RestApiConfig { } @Bean - Supplier restTemplate(@Autowired RestTemplateBuilder builder) { + Supplier restTemplateSupplier(@Autowired RestTemplateBuilder builder) { return builder::build; } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/error/ApplicationErrorHandler.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/error/ApplicationErrorHandler.java new file mode 100644 index 0000000..aa0fc81 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/error/ApplicationErrorHandler.java @@ -0,0 +1,37 @@ +package ru.dragonestia.picker.cp.error; + +import com.vaadin.flow.component.UI; +import com.vaadin.flow.server.Command; +import com.vaadin.flow.server.ErrorEvent; +import com.vaadin.flow.server.ErrorHandler; +import lombok.extern.log4j.Log4j2; +import ru.dragonestia.picker.api.exception.ApiException; +import ru.dragonestia.picker.cp.component.Notifications; + +@Log4j2 +public class ApplicationErrorHandler implements ErrorHandler { + + @Override + public void error(ErrorEvent errorEvent) { + if (UI.getCurrent() == null) { + log.throwing(errorEvent.getThrowable()); + return; + } + + if (errorEvent.getThrowable() instanceof ApiException ex) { + execute(() -> { + Notifications.error(ex.getMessage()); + }); + return; + } + + execute(() -> { + Notifications.error("Internal server error"); + }); + log.throwing(errorEvent.getThrowable()); + } + + private void execute(Command command) { + UI.getCurrent().access(command); + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/listener/VaadinEventListener.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/listener/VaadinEventListener.java new file mode 100644 index 0000000..d60e003 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/listener/VaadinEventListener.java @@ -0,0 +1,19 @@ +package ru.dragonestia.picker.cp.listener; + +import com.vaadin.flow.server.ServiceInitEvent; +import com.vaadin.flow.server.VaadinServiceInitListener; +import com.vaadin.flow.spring.annotation.SpringComponent; +import lombok.extern.log4j.Log4j2; +import ru.dragonestia.picker.cp.error.ApplicationErrorHandler; + +@Log4j2 +@SpringComponent +public class VaadinEventListener implements VaadinServiceInitListener { + + @Override + public void serviceInit(ServiceInitEvent event) { + event.getSource().addSessionInitListener(e -> { + e.getSession().setErrorHandler(new ApplicationErrorHandler()); + }); + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Node.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Node.java deleted file mode 100644 index 046167a..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Node.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.dragonestia.picker.cp.model; - -import lombok.NonNull; -import ru.dragonestia.picker.cp.model.type.PickingMode; - -import java.io.Serializable; - -public record Node(@NonNull String id, @NonNull PickingMode mode) implements Serializable { - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object object) { - if (object == this) return true; - if (object == null) return false; - if (object instanceof Node other) { - return id.equals(other.id); - } - return false; - } -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Room.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Room.java deleted file mode 100644 index 35d3a97..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/Room.java +++ /dev/null @@ -1,71 +0,0 @@ -package ru.dragonestia.picker.cp.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import ru.dragonestia.picker.cp.model.type.SlotLimit; - -import java.net.URI; - -@Getter -public class Room { - - private final String id; - private final String nodeId; - private final SlotLimit slots; - private final String payload; - private boolean locked = false; - - @JsonCreator - private Room(@JsonProperty("id") String id, - @JsonProperty("nodeIdentifier") String nodeId, - @JsonProperty("slots") SlotLimit slots, - @JsonProperty("payload") String payload, - @JsonProperty("locked") boolean locked) { - - this.id = id; - this.nodeId = nodeId; - this.slots = slots; - this.payload = payload; - this.locked = locked; - } - - public static Room create(String roomId, Node node, SlotLimit limit, String payload) { - return new Room(roomId, node.id(), limit, payload, false); - } - - public void setLocked(boolean value) { - locked = value; - } - - public boolean isAvailable(int usedSlots, int requiredSlots) { - if (locked) return false; - if (slots.isUnlimited()) return true; - return slots.slots() >= usedSlots + requiredSlots; - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object object) { - if (object == this) return true; - if (object == null) return false; - if (object instanceof Room other) { - return id.equals(other.id); - } - return false; - } - - public URI createApiURI() { - return URI.create("/nodes/" + nodeId + "/rooms/" + id); - } - - public String getUsingPercentage(int used) { - if (getSlots().isUnlimited()) return "0%"; - double percent = used / (double) getSlots().slots() * 100; - return ((int) percent) + "%"; - } -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/dto/RoomDTO.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/dto/RoomDTO.java deleted file mode 100644 index 4e13f95..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/dto/RoomDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.dragonestia.picker.cp.model.dto; - -import ru.dragonestia.picker.cp.model.Node; - -import java.net.URI; - -public record RoomDTO(String id, int slots, boolean locked) { - - public URI uriAPI(Node node) { - return uriAPI(node.id()); - } - - public URI uriAPI(String nodeId) { - return URI.create("/nodes/" + nodeId + "/rooms/" + id); - } -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/SlotLimit.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/SlotLimit.java deleted file mode 100644 index ed74737..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/model/type/SlotLimit.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dragonestia.picker.cp.model.type; - -import java.beans.Transient; - -public record SlotLimit(int slots) { - - private final static int UNLIMITED_VALUE = -1; - - public static SlotLimit unlimited() { - return new SlotLimit(UNLIMITED_VALUE); - } - - public static SlotLimit of(int slots) { - return new SlotLimit(slots); - } - - @Transient - public boolean isUnlimited() { - return slots == UNLIMITED_VALUE; - } -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/HomePage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/HomePage.java index 981d68a..7487181 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/HomePage.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/HomePage.java @@ -3,10 +3,11 @@ package ru.dragonestia.picker.cp.page; import com.vaadin.flow.component.html.H1; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.*; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; @Route("/") -public class HomePage extends VerticalLayout { +public class HomePage extends VerticalLayout implements BeforeEnterObserver { public HomePage() { super(); @@ -14,4 +15,9 @@ public class HomePage extends VerticalLayout { add(new H1("Hello world!")); add(new Paragraph("Hello world!")); } + + @Override + public void beforeEnter(BeforeEnterEvent beforeEnterEvent) { + throw new NodeNotFoundException("gdfsg"); + } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodeDetailsPage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodeDetailsPage.java index 42240b8..586898e 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodeDetailsPage.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodeDetailsPage.java @@ -4,72 +4,54 @@ import com.vaadin.flow.component.Html; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Hr; import com.vaadin.flow.component.html.Paragraph; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; 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.Getter; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.model.Room; +import ru.dragonestia.picker.api.repository.NodeRepository; +import ru.dragonestia.picker.api.repository.RoomRepository; +import ru.dragonestia.picker.cp.component.Notifications; import ru.dragonestia.picker.cp.component.RoomList; import ru.dragonestia.picker.cp.component.NavPath; import ru.dragonestia.picker.cp.component.RegisterRoom; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.model.dto.RoomDTO; -import ru.dragonestia.picker.cp.repository.RoomRepository; -import ru.dragonestia.picker.cp.repository.NodeRepository; +import ru.dragonestia.picker.cp.util.RouteParamsExtractor; import java.util.List; @Getter +@RequiredArgsConstructor @PageTitle("Rooms") @Route("/nodes/:nodeId") public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver { private final NodeRepository nodeRepository; private final RoomRepository roomRepository; + private final RouteParamsExtractor paramsExtractor; + private Node node; private RegisterRoom registerRoom; private RoomList roomList; - public NodeDetailsPage(@Autowired NodeRepository nodeRepository, - @Autowired RoomRepository roomRepository) { - - this.nodeRepository = nodeRepository; - this.roomRepository = roomRepository; - } - @Override public void beforeEnter(BeforeEnterEvent event) { - var nodeIdOpt = event.getRouteParameters().get("nodeId"); - if (nodeIdOpt.isEmpty()) { - getUI().ifPresent(ui -> ui.navigate("/nodes")); - return; - } - var nodeId = nodeIdOpt.get(); - add(new NavPath(new NavPath.Point("Nodes", "/nodes"), new NavPath.Point(nodeId, "/nodes/" + nodeId))); - - var nodeOpt = nodeRepository.find(nodeId); - if (nodeOpt.isEmpty()) { - add(new H2("Error 404")); - add(new Paragraph("Node not found")); - Notification.show("Node '" + nodeId + "' does not exist", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); - return; - } - node = nodeOpt.get(); + node = paramsExtractor.extractNodeId(event); initComponents(node, roomRepository.all(node)); } - private void initComponents(Node node, List rooms) { + private void initComponents(Node node, List rooms) { + add(NavPath.toNode(node.getId())); printNodeDetails(node); add(new Hr()); - add(registerRoom = new RegisterRoom(node, (bucket) -> { + add(registerRoom = new RegisterRoom(node, (room) -> { try { - roomRepository.register(bucket); + roomRepository.register(room); return new RegisterRoom.Response(false, null); } catch (Error error) { return new RegisterRoom.Response(true, error.getMessage()); @@ -78,9 +60,9 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv } })); add(new Hr()); - add(roomList = new RoomList(node.id(), rooms)); - roomList.setRemoveMethod(bucket -> { - roomRepository.remove(node, bucket); + add(roomList = new RoomList(node.getId(), rooms)); + roomList.setRemoveMethod(room -> { + roomRepository.remove(node, room); roomList.update(roomRepository.all(node)); }); } @@ -89,8 +71,8 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv add(new H2("Node details")); var layout = new VerticalLayout(); - layout.add(new Html("Identifier: " + node.id() + "")); - layout.add(new Html("Mode: " + node.mode().getName() + "")); + layout.add(new Html("Identifier: " + node.getId() + "")); + layout.add(new Html("Mode: " + node.getMode().getName() + "")); add(layout); } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodesPage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodesPage.java index d3a746b..d894490 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodesPage.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/NodesPage.java @@ -7,10 +7,11 @@ import com.vaadin.flow.router.Route; 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.repository.NodeRepository; import ru.dragonestia.picker.cp.component.NavPath; import ru.dragonestia.picker.cp.component.NodeList; import ru.dragonestia.picker.cp.component.RegisterNode; -import ru.dragonestia.picker.cp.repository.NodeRepository; @Log4j2 @Getter @@ -26,7 +27,7 @@ public class NodesPage extends VerticalLayout { super(); this.nodeRepository = nodeRepository; - add(new NavPath(new NavPath.Point("Nodes", "/nodes"))); + add(NavPath.rootNodes()); add(registerNode = createRegisterNodeElement()); add(new Hr()); add(nodeList = createNodeListElement()); @@ -41,10 +42,7 @@ public class NodesPage extends VerticalLayout { try { nodeRepository.register(node); return new RegisterNode.Response(false, ""); - } catch (Error ex) { - return new RegisterNode.Response(true, ex.getMessage()); - } catch (RuntimeException ex) { - log.throwing(ex); + } catch (ApiException ex) { return new RegisterNode.Response(true, ex.getMessage()); } finally { nodeList.update(nodeRepository.all()); diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/RoomDetailsPage.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/RoomDetailsPage.java index 8a69751..6082473 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/RoomDetailsPage.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/RoomDetailsPage.java @@ -8,28 +8,30 @@ import com.vaadin.flow.component.html.Hr; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextArea; 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 org.springframework.beans.factory.annotation.Autowired; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.model.Room; +import ru.dragonestia.picker.api.model.User; +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.cp.component.AddUsers; import ru.dragonestia.picker.cp.component.NavPath; +import ru.dragonestia.picker.cp.component.Notifications; import ru.dragonestia.picker.cp.component.UserList; -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.model.User; -import ru.dragonestia.picker.cp.repository.RoomRepository; -import ru.dragonestia.picker.cp.repository.NodeRepository; -import ru.dragonestia.picker.cp.repository.UserRepository; +import ru.dragonestia.picker.cp.util.RouteParamsExtractor; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; +@RequiredArgsConstructor @PageTitle("Room details") @Route("/nodes/:nodeId/rooms/:roomId") public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver { @@ -37,6 +39,8 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv private final NodeRepository nodeRepository; private final RoomRepository roomRepository; private final UserRepository userRepository; + private final RouteParamsExtractor paramsExtractor; + private Node node; private Room room; private AddUsers addUsers; @@ -44,57 +48,16 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv private Button lockRoomButton; private VerticalLayout roomInfo; - @Autowired - public RoomDetailsPage(NodeRepository nodeRepository, RoomRepository roomRepository, UserRepository userRepository) { - this.nodeRepository = nodeRepository; - this.roomRepository = roomRepository; - this.userRepository = userRepository; - } - @Override public void beforeEnter(BeforeEnterEvent event) { - var nodeIdOpt = event.getRouteParameters().get("nodeId"); - if (nodeIdOpt.isEmpty()) { - getUI().ifPresent(ui -> ui.navigate("/nodes")); - return; - } - - var roomIdOpt = event.getRouteParameters().get("roomId"); - if (roomIdOpt.isEmpty()) { - getUI().ifPresent(ui -> ui.navigate("/rooms/" + nodeIdOpt.get())); - return; - } - - var nodeId = nodeIdOpt.get(); - var roomId = roomIdOpt.get(); - add(new NavPath(new NavPath.Point("Nodes", "/nodes"), - new NavPath.Point(nodeId, "/nodes/" + nodeId), - new NavPath.Point(roomId, "/nodes/" + nodeId + "/rooms/" + roomId))); - - var nodeOpt = nodeRepository.find(nodeId); - if (nodeOpt.isEmpty()) { - add(new H2("Error 404")); - add(new Paragraph("Node not found!")); - Notification.show("Node '" + nodeId + "' does not exist", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); - return; - } - node = nodeOpt.get(); - - var bucketOpt = roomRepository.find(node, roomId); - if (bucketOpt.isEmpty()) { - add(new H2("Error 404")); - add(new Paragraph("Room not found!")); - Notification.show("Room '" + nodeId + "' does not exist", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); - return; - } - room = bucketOpt.get(); + node = paramsExtractor.extractNodeId(event); + room = paramsExtractor.extractRoomId(event, node); init(); } private void init() { + add(NavPath.toRoom(node.getId(), room.getId())); add(new H2("Room details")); printRoomDetails(); add(new Hr()); @@ -108,7 +71,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv roomInfo.removeAll(); roomInfo.add(new Html("Node identifier: " + room.getNodeId() + "")); roomInfo.add(new Html("Room identifier: " + room.getId() + "")); - roomInfo.add(new Html("Slots: " + (room.getSlots().isUnlimited()? "Unlimited" : room.getSlots().slots()) + "")); + roomInfo.add(new Html("Slots: " + (room.isUnlimited()? "Unlimited" : room.getSlots()) + "")); roomInfo.add(new Html("Locked: " + (room.isLocked()? "Yes" : "No") + "")); } @@ -139,20 +102,13 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv private void changeBucketLockedState() { var newValue = !room.isLocked(); - try { - roomRepository.lock(room, newValue); - } catch (Error error) { - Notification.show(error.getMessage(), 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); - return; - } + roomRepository.lock(room, newValue); room.setLocked(newValue); setLockRoomButtonState(); updateRoomInfo(); - Notification.show("Success", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Success"); } private void appendUsers(Room room, Collection users, boolean ignoreLimitation) { @@ -160,7 +116,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv var newUsers = users.stream() .filter(user -> { - if (user.id().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) { + if (user.getId().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) { return true; } @@ -173,15 +129,12 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv if (validationFail.get()) { if (newUsers.isEmpty()) { - Notification.show("All users entered were added because they do not comply with the rule for writing the user identifier", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_ERROR); + Notifications.error("All users entered were added because they do not comply with the rule for writing the user identifier"); } else { - Notification.show("Not all users entered were added because they do not comply with the rule for writing the user identifier", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_WARNING); + Notifications.warn("Not all users entered were added because they do not comply with the rule for writing the user identifier"); } } else { - Notification.show("Success", 3000, Notification.Position.TOP_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + Notifications.success("Success"); } } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/ErrorPlug.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/ErrorPlug.java new file mode 100644 index 0000000..fe474d7 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/ErrorPlug.java @@ -0,0 +1,15 @@ +package ru.dragonestia.picker.cp.page.plug; + +import com.vaadin.flow.component.Html; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import ru.dragonestia.picker.cp.component.NavPath; + +public abstract class ErrorPlug extends VerticalLayout { + + public void init(NavPath path, String title, String description) { + add(path); + add(new H1(title)); + add(new Html("

" + description + "

")); + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidNodeIdentifierPlug.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidNodeIdentifierPlug.java new file mode 100644 index 0000000..c054392 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidNodeIdentifierPlug.java @@ -0,0 +1,20 @@ +package ru.dragonestia.picker.cp.page.plug; + +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.ErrorParameter; +import com.vaadin.flow.router.HasErrorParameter; +import jakarta.servlet.http.HttpServletResponse; +import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException; +import ru.dragonestia.picker.cp.component.NavPath; + +public class InvalidNodeIdentifierPlug extends ErrorPlug implements HasErrorParameter { + + @Override + public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter errorParameter) { + var ex = errorParameter.getException(); + var nodeId = ex.getNodeId(); + + init(NavPath.toNode(nodeId), "Error 400", ex.getMessage()); + return HttpServletResponse.SC_NOT_FOUND; + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidRoomIdentifierPlug.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidRoomIdentifierPlug.java new file mode 100644 index 0000000..f0d929b --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/InvalidRoomIdentifierPlug.java @@ -0,0 +1,21 @@ +package ru.dragonestia.picker.cp.page.plug; + +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.ErrorParameter; +import com.vaadin.flow.router.HasErrorParameter; +import jakarta.servlet.http.HttpServletResponse; +import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException; +import ru.dragonestia.picker.cp.component.NavPath; + +public class InvalidRoomIdentifierPlug extends ErrorPlug implements HasErrorParameter { + + @Override + public int setErrorParameter(BeforeEnterEvent event, ErrorParameter errorParameter) { + var ex = errorParameter.getException(); + var nodeId = ex.getNodeId(); + var roomId = ex.getRoomId(); + + init(NavPath.toRoom(nodeId, roomId), "Error 400", ex.getMessage()); + return HttpServletResponse.SC_NOT_FOUND; + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/NodeNotFoundPlug.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/NodeNotFoundPlug.java new file mode 100644 index 0000000..1aebdd1 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/NodeNotFoundPlug.java @@ -0,0 +1,20 @@ +package ru.dragonestia.picker.cp.page.plug; + +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.ErrorParameter; +import com.vaadin.flow.router.HasErrorParameter; +import jakarta.servlet.http.HttpServletResponse; +import ru.dragonestia.picker.api.exception.NodeNotFoundException; +import ru.dragonestia.picker.cp.component.NavPath; + +public class NodeNotFoundPlug extends ErrorPlug implements HasErrorParameter { + + @Override + public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter errorParameter) { + var ex = errorParameter.getException(); + var nodeId = ex.getNodeId(); + + init(NavPath.toNode(nodeId), "Error 404", ex.getMessage()); + return HttpServletResponse.SC_NOT_FOUND; + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/RoomNotFoundPlug.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/RoomNotFoundPlug.java new file mode 100644 index 0000000..3b56b93 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/page/plug/RoomNotFoundPlug.java @@ -0,0 +1,21 @@ +package ru.dragonestia.picker.cp.page.plug; + +import com.vaadin.flow.router.BeforeEnterEvent; +import com.vaadin.flow.router.ErrorParameter; +import com.vaadin.flow.router.HasErrorParameter; +import jakarta.servlet.http.HttpServletResponse; +import ru.dragonestia.picker.api.exception.RoomNotFoundException; +import ru.dragonestia.picker.cp.component.NavPath; + +public class RoomNotFoundPlug extends ErrorPlug implements HasErrorParameter { + + @Override + public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter errorParameter) { + var ex = errorParameter.getException(); + var nodeId = ex.getNodeId(); + var roomId = ex.getRoomId(); + + init(NavPath.toRoom(nodeId, roomId), "Error 404", ex.getMessage()); + return HttpServletResponse.SC_NOT_FOUND; + } +} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/NodeRepository.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/NodeRepository.java deleted file mode 100644 index 8a4fdda..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/NodeRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.dragonestia.picker.cp.repository; - -import ru.dragonestia.picker.cp.model.Node; - -import java.util.List; -import java.util.Optional; - -public interface NodeRepository { - - void register(Node node); - - List all(); - - Optional find(String nodeId); - - void remove(String nodeId); -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/RoomRepository.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/RoomRepository.java deleted file mode 100644 index 0cfa563..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/RoomRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.dragonestia.picker.cp.repository; - -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.model.dto.RoomDTO; - -import java.util.List; -import java.util.Optional; - -public interface RoomRepository { - - List all(Node node); - - void register(Room room); - - void remove(Room room); - - void remove(Node node, RoomDTO bucket); - - Optional find(Node node, String roomId); - - void lock(Room room, boolean value); -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/UserRepository.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/UserRepository.java deleted file mode 100644 index f2bcdd2..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/UserRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.dragonestia.picker.cp.repository; - -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.User; - -import java.util.Collection; -import java.util.List; - -public interface UserRepository { - - void linkWithRoom(Room room, Collection users, boolean force); - - void unlinkFromRoom(Room room, Collection users); - - List all(Room room); -} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/NodeRepositoryImpl.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/NodeRepositoryImpl.java index d26300d..95447f6 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/NodeRepositoryImpl.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/NodeRepositoryImpl.java @@ -3,13 +3,15 @@ package ru.dragonestia.picker.cp.repository.impl; import com.vaadin.flow.spring.annotation.SpringComponent; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.repository.NodeRepository; -import ru.dragonestia.picker.cp.repository.impl.response.NodeDetailsResponse; -import ru.dragonestia.picker.cp.repository.impl.response.NodeListResponse; -import ru.dragonestia.picker.cp.repository.impl.response.NodeRegisterResponse; +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.repository.response.NodeDetailsResponse; +import ru.dragonestia.picker.api.repository.response.NodeListResponse; +import ru.dragonestia.picker.api.model.Node; +import ru.dragonestia.picker.api.repository.NodeRepository; -import java.net.URI; import java.util.List; import java.util.Optional; @@ -21,41 +23,30 @@ public class NodeRepositoryImpl implements NodeRepository { private final RestUtil rest; @Override - public void register(Node node) { - NodeRegisterResponse response; - try { - response = rest.post(URI.create("nodes"), - NodeRegisterResponse.class, - params -> { - params.put("nodeId", node.id()); - params.put("method", node.mode().name()); - }); - } catch (Exception ex) { - throw new RuntimeException("Internal error", ex); - } - - if (!response.success()) { - throw new Error(response.message()); - } + public void register(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException { + rest.query("nodes", HttpMethod.POST, params -> { + params.put("nodeId", node.getId()); + params.put("method", node.getMode().name()); + }); } @Override public List all() { - return rest.get(URI.create("nodes"), NodeListResponse.class).nodes(); + return rest.query("nodes", HttpMethod.GET, NodeListResponse.class, params -> {}).nodes(); } @Override public Optional find(String nodeId) { try { - var response = rest.get(URI.create("nodes/" + nodeId), NodeDetailsResponse.class); + var response = rest.query("nodes/" + nodeId, HttpMethod.GET, NodeDetailsResponse.class, params -> {}); return Optional.of(response.node()); - } catch (Exception ex) { + } catch (NodeNotFoundException ex) { return Optional.empty(); } } @Override public void remove(String nodeId) { - rest.delete(URI.create("nodes/" + nodeId), params -> {}); + rest.query("nodes/" + nodeId, HttpMethod.DELETE, params -> {}); } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RestUtil.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RestUtil.java index bc27fe5..8b72a54 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RestUtil.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RestUtil.java @@ -1,15 +1,15 @@ package ru.dragonestia.picker.cp.repository.impl; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; +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.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; @@ -18,67 +18,41 @@ import java.util.function.Supplier; public class RestUtil { private final URI serverUrl; - private final Supplier restTemplate; + private final Supplier restTemplateSupplier; - public T get(URI uri, Class responseType) { - var template = restTemplate.get(); - return Objects.requireNonNull(template.getForObject(serverUrl.resolve(uri), responseType)); + public void query(String uri, HttpMethod method) { + query(uri, method, ParamsConsumer.NONE); } - public ResponseEntity getEntity(URI uri, Class responseType) { - var template = restTemplate.get(); - return template.getForEntity(serverUrl.resolve(uri), responseType); - } - - public T get(URI uri, Class responseType, Consumer> paramsConsumer) { + public void query(String uri, HttpMethod method, ParamsConsumer paramsConsumer) { var params = new HashMap(); paramsConsumer.accept(params); - var template = restTemplate.get(); - return Objects.requireNonNull(template.getForObject(buildPath(uri, params.keySet()), - responseType, - 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 post(URI uri, Class responseType, Consumer> paramsConsumer) { + public T query(String uri, HttpMethod method, Class clazz) { + return query(uri, method, clazz, ParamsConsumer.NONE); + } + + public T query(String uri, HttpMethod method, Class clazz, ParamsConsumer paramsConsumer) { var params = new HashMap(); paramsConsumer.accept(params); - var template = restTemplate.get(); - return Objects.requireNonNull(template.postForObject(buildPath(uri, params.keySet()), - null, - responseType, - 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))); + } } - public ResponseEntity postEntity(URI uri, Class responseType, Consumer> paramsConsumer) { - var params = new HashMap(); - paramsConsumer.accept(params); - - var template = restTemplate.get(); - return template.postForEntity(buildPath(uri, params.keySet()), - null, - responseType, - params); - } - - public void put(URI uri, Consumer> paramsConsumer) { - var params = new HashMap(); - paramsConsumer.accept(params); - - var template = restTemplate.get(); - template.put(buildPath(uri, params.keySet()), params); - } - - public void delete(URI uri, Consumer> paramsConsumer) { - var params = new HashMap(); - paramsConsumer.accept(params); - - var template = restTemplate.get(); - template.delete(buildPath(uri, params.keySet()), params); - } - - private String buildPath(URI uri, Collection paramKeys) { + private String buildPath(String uri, Collection paramKeys) { var path = new StringBuilder(serverUrl.resolve(uri) + "?"); int left = paramKeys.size(); for (var key: paramKeys) { @@ -92,4 +66,9 @@ public class RestUtil { } return path.toString(); } + + public interface ParamsConsumer extends Consumer> { + + ParamsConsumer NONE = map -> {}; + } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RoomRepositoryImpl.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RoomRepositoryImpl.java index 8ca8528..d29ca69 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RoomRepositoryImpl.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/RoomRepositoryImpl.java @@ -3,18 +3,18 @@ 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.web.client.HttpClientErrorException; -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.Node; -import ru.dragonestia.picker.cp.model.dto.RoomDTO; -import ru.dragonestia.picker.cp.repository.RoomRepository; -import ru.dragonestia.picker.cp.repository.impl.response.RoomInfoResponse; -import ru.dragonestia.picker.cp.repository.impl.response.RoomListResponse; -import ru.dragonestia.picker.cp.repository.impl.response.RoomRegisterResponse; +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.Node; +import ru.dragonestia.picker.api.model.Room; +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.net.URI; import java.util.List; -import java.util.Objects; import java.util.Optional; @Log4j2 @@ -25,76 +25,44 @@ public class RoomRepositoryImpl implements RoomRepository { private final RestUtil rest; @Override - public List all(Node node) { - var entity = rest.getEntity(URI.create("/nodes/" + node.id() + "/rooms"), - RoomListResponse.class); - - if (entity.getStatusCode().value() == 404) { - throw new Error("Node with identifier '" + node.id() + "' does not exists'"); - } - - if (!entity.hasBody()) { - throw new Error("Room list did not present"); - } - - return Objects.requireNonNull(entity.getBody()).rooms(); + public void register(Room room) 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())); + }); } @Override - public void register(Room room) { + public void remove(Room room) throws NodeNotFoundException { + rest.query("/nodes/" + room.getNodeId() + "/rooms/" + room.getId(), HttpMethod.DELETE, params -> {}); + } + + @Override + public void remove(Node node, Room.Short room) throws NodeNotFoundException { + rest.query("/nodes/" + node.getId() + "/rooms/" + room.id(), HttpMethod.DELETE, params -> {}); + } + + @Override + public List all(Node node) throws NodeNotFoundException { + return rest.query("/nodes/" + node.getId() + "/rooms", HttpMethod.GET, RoomListResponse.class, params -> {}).rooms(); + } + + @Override + public Optional find(Node node, String roomId) throws NodeNotFoundException { try { - var response = rest.post(URI.create("/nodes/" + room.getNodeId() + "/rooms"), - RoomRegisterResponse.class, - params -> { - params.put("roomId", room.getId()); - params.put("slots", Integer.toString(room.getSlots().slots())); - params.put("payload", room.getPayload()); - params.put("locked", Boolean.toString(room.isLocked())); - }); - - if (response.success()) return; - throw new Error(response.message()); - } catch (HttpClientErrorException ex) { - var response = ex.getResponseBodyAs(RoomRegisterResponse.class); - - if (response != null) { - throw new Error(response.message()); - } - - log.throwing(ex); - throw new Error("Internal error. Check logs"); - } - } - - @Override - public void remove(Room room) { - rest.delete(URI.create("/nodes/" + room.getNodeId() + "/rooms/" + room.getId()), params -> {}); - } - - @Override - public void remove(Node node, RoomDTO room) { - rest.delete(URI.create("/nodes/" + node.id() + "/rooms/" + room.id()), params -> {}); - } - - @Override - public Optional find(Node node, String roomId) { - try { - var response = rest.get(URI.create("/nodes/" + node.id() + "/rooms/" + roomId), RoomInfoResponse.class, map -> {}); + var response = rest.query("/nodes/" + node.getId() + "/rooms/" + roomId, HttpMethod.GET, RoomInfoResponse.class, map -> {}); return Optional.of(response.room()); - } catch (Exception ex) { + } catch (RoomNotFoundException ex) { return Optional.empty(); } } @Override - public void lock(Room room, boolean value) { - try { - rest.post(URI.create(room.createApiURI() + "/lock"), Boolean.class, params -> { - params.put("newState", Boolean.toString(value)); - }); - } catch (Exception ex) { - log.throwing(ex); - throw new Error("Error when changing room locked state"); - } + public void lock(Room 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)); + }); } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java index 90a1e56..b4098ac 100644 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/UserRepositoryImpl.java @@ -3,13 +3,15 @@ package ru.dragonestia.picker.cp.repository.impl; import com.vaadin.flow.spring.annotation.SpringComponent; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import ru.dragonestia.picker.cp.model.Room; -import ru.dragonestia.picker.cp.model.User; -import ru.dragonestia.picker.cp.repository.UserRepository; -import ru.dragonestia.picker.cp.repository.impl.response.LinkUsersWithRoomResponse; -import ru.dragonestia.picker.cp.repository.impl.response.RoomUserListResponse; +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; +import ru.dragonestia.picker.api.model.User; +import ru.dragonestia.picker.api.repository.UserRepository; +import ru.dragonestia.picker.api.repository.response.RoomUserListResponse; -import java.net.URI; import java.util.Collection; import java.util.List; @@ -21,47 +23,29 @@ public class UserRepositoryImpl implements UserRepository { private final RestUtil rest; @Override - public void linkWithRoom(Room room, Collection users, boolean force) { - try { - var response = rest.post(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())), - LinkUsersWithRoomResponse.class, - params -> { - params.put("userIds", String.join(",", users.stream().map(User::id).toList())); - params.put("force", Boolean.toString(force)); - } - ); - - if (!response.success()) { - throw new Error(response.message()); - } - } catch (Exception ex) { - log.throwing(ex); - throw new Error("Internal error"); - } + public void linkWithRoom(Room room, Collection 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(User::getId).toList())); + params.put("force", Boolean.toString(force)); + }); } @Override - public void unlinkFromRoom(Room room, Collection users) { - try { - rest.delete(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())), - params -> params.put("userIds", String.join(",", users.stream().map(User::id).toList()))); - } catch (Exception ex) { - log.throwing(ex); - throw new Error("Internal error"); - } + public void unlinkFromRoom(Room room, Collection 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(User::getId).toList())); + }); } @Override - public List all(Room room) { - try { - var response = rest.get(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())), - RoomUserListResponse.class, - params -> {}); - - return response.users(); - } catch (Exception ex) { - log.throwing(ex); - throw new Error("Internal error"); - } + public List all(Room room) throws NodeNotFoundException, RoomNotFoundException { + return rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()), + HttpMethod.GET, + RoomUserListResponse.class, + params -> {}).users(); } } diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/LinkUsersWithRoomResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/LinkUsersWithRoomResponse.java deleted file mode 100644 index 04f19be..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/LinkUsersWithRoomResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -public record LinkUsersWithRoomResponse(boolean success, String message, int usedSlots, int totalSlots) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeDetailsResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeDetailsResponse.java deleted file mode 100644 index 92315fd..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeDetailsResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -import ru.dragonestia.picker.cp.model.Node; - -public record NodeDetailsResponse(Node node) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeListResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeListResponse.java deleted file mode 100644 index 1877b0b..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeListResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -import ru.dragonestia.picker.cp.model.Node; - -import java.util.List; - -public record NodeListResponse(List nodes) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeRegisterResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeRegisterResponse.java deleted file mode 100644 index f5bbb34..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/NodeRegisterResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -public record NodeRegisterResponse(boolean success, String message) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomInfoResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomInfoResponse.java deleted file mode 100644 index 9f3dfcd..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomInfoResponse.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -import ru.dragonestia.picker.cp.model.Room; - -public record RoomInfoResponse(Room room) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomListResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomListResponse.java deleted file mode 100644 index 5dbe758..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomListResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -import ru.dragonestia.picker.cp.model.dto.RoomDTO; - -import java.util.List; - -public record RoomListResponse(String node, List rooms) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomRegisterResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomRegisterResponse.java deleted file mode 100644 index 86adffe..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomRegisterResponse.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -public record RoomRegisterResponse(boolean success, String message) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomUserListResponse.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomUserListResponse.java deleted file mode 100644 index abc3c22..0000000 --- a/control-panel/src/main/java/ru/dragonestia/picker/cp/repository/impl/response/RoomUserListResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.picker.cp.repository.impl.response; - -import ru.dragonestia.picker.cp.model.User; - -import java.util.List; - -public record RoomUserListResponse(int slots, int usedSlots, List users) {} diff --git a/control-panel/src/main/java/ru/dragonestia/picker/cp/util/RouteParamsExtractor.java b/control-panel/src/main/java/ru/dragonestia/picker/cp/util/RouteParamsExtractor.java new file mode 100644 index 0000000..2ccefb7 --- /dev/null +++ b/control-panel/src/main/java/ru/dragonestia/picker/cp/util/RouteParamsExtractor.java @@ -0,0 +1,29 @@ +package ru.dragonestia.picker.cp.util; + +import com.vaadin.flow.router.BeforeEnterEvent; +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.model.Node; +import ru.dragonestia.picker.api.model.Room; +import ru.dragonestia.picker.api.repository.NodeRepository; +import ru.dragonestia.picker.api.repository.RoomRepository; + +@Component +@RequiredArgsConstructor +public class RouteParamsExtractor { + + private final NodeRepository nodeRepository; + private final RoomRepository roomRepository; + + public Node extractNodeId(BeforeEnterEvent e) throws NodeNotFoundException { + var nodeId = e.getRouteParameters().get("nodeId").orElseThrow(() -> new NodeNotFoundException("null")); + return nodeRepository.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId)); + } + + public Room extractRoomId(BeforeEnterEvent e, Node node) throws RoomNotFoundException { + var roomId = e.getRouteParameters().get("roomId").orElseThrow(() -> new NodeNotFoundException("null")); + return roomRepository.find(node, roomId).orElseThrow(() -> new NodeNotFoundException(roomId)); + } +} diff --git a/settings.gradle b/settings.gradle index fca70a7..4d6c145 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,5 @@ rootProject.name = 'RoomPicker' include 'app' include 'control-panel' +include 'api' +