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
This commit is contained in:
parent
01518f4054
commit
7566f24d36
16
api/build.gradle
Normal file
16
api/build.gradle
Normal file
@ -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()
|
||||||
|
}
|
||||||
@ -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<String, String> details);
|
||||||
|
}
|
||||||
@ -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<String, Constructor> factory = init();
|
||||||
|
|
||||||
|
private ExceptionFactory() {}
|
||||||
|
|
||||||
|
private static Map<String, Constructor> init() {
|
||||||
|
var factory = new HashMap<String, Constructor>();
|
||||||
|
|
||||||
|
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<ErrorResponse, ApiException> {}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("identifier", getNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("nodeId", getNodeId());
|
||||||
|
details.put("roomId", getRoomId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String> givenUsernames;
|
||||||
|
private final List<String> invalidUsernames;
|
||||||
|
|
||||||
|
public InvalidUsernamesException(List<String> givenUsernames, List<String> 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<String> getGivenUsernames() {
|
||||||
|
return givenUsernames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getInvalidUsernames() {
|
||||||
|
return invalidUsernames;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getErrorId() {
|
||||||
|
return ERROR_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendDetailsToErrorResponse(Map<String, String> details) {
|
||||||
|
Function<Collection<String>, String> toString = input -> String.join(",", input);
|
||||||
|
|
||||||
|
details.put("givenUsernames", toString.apply(getGivenUsernames()));
|
||||||
|
details.put("invalidUsernames", toString.apply(getInvalidUsernames()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("nodeId", getNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("nodeId", getNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("node", getNodeId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("nodeId", getNodeId());
|
||||||
|
details.put("roomId", getRoomId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("nodeId", getNodeId());
|
||||||
|
details.put("roomId", getRoomId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<String, String> details) {
|
||||||
|
details.put("node", getNodeId());
|
||||||
|
details.put("room", getRoomId());
|
||||||
|
}
|
||||||
|
}
|
||||||
39
api/src/main/java/ru/dragonestia/picker/api/model/Node.java
Normal file
39
api/src/main/java/ru/dragonestia/picker/api/model/Node.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
73
api/src/main/java/ru/dragonestia/picker/api/model/Room.java
Normal file
73
api/src/main/java/ru/dragonestia/picker/api/model/Room.java
Normal file
@ -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) {}
|
||||||
|
}
|
||||||
@ -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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
@ -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 {
|
public enum PickingMode {
|
||||||
SEQUENTIAL_FILLING("Sequential filling"),
|
SEQUENTIAL_FILLING("Sequential filling"),
|
||||||
ROUND_ROBIN("Round Robin"),
|
ROUND_ROBIN("Round Robin"),
|
||||||
LEAST_PICKED("Least Picked");
|
LEAST_PICKED("Least Picked");
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
PickingMode(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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<Node> all();
|
||||||
|
|
||||||
|
Optional<Node> find(String nodeId);
|
||||||
|
|
||||||
|
void remove(String nodeId);
|
||||||
|
}
|
||||||
@ -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<Room.Short> all(Node node) throws NodeNotFoundException;
|
||||||
|
|
||||||
|
Optional<Room> find(Node node, String roomId) throws NodeNotFoundException;
|
||||||
|
|
||||||
|
void lock(Room room, boolean value) throws NodeNotFoundException, RoomNotFoundException;
|
||||||
|
}
|
||||||
@ -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<User> users, boolean force) throws NodeNotFoundException, RoomNotFoundException, RoomAreFullException;
|
||||||
|
|
||||||
|
void unlinkFromRoom(Room room, Collection<User> users) throws NodeNotFoundException, RoomNotFoundException;
|
||||||
|
|
||||||
|
List<User> all(Room room) throws NodeNotFoundException, RoomNotFoundException;
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package ru.dragonestia.picker.api.repository.response;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public record ErrorResponse(String errorId, String message, Map<String, String> details) {}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
package ru.dragonestia.picker.api.repository.response;
|
||||||
|
|
||||||
|
public record LinkUsersWithRoomResponse(int usedSlots, int totalSlots) {}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package ru.dragonestia.picker.api.repository.response;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.model.Node;
|
||||||
|
|
||||||
|
public record NodeDetailsResponse(Node node) {}
|
||||||
@ -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<Node> nodes) {}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package ru.dragonestia.picker.api.repository.response;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.model.Room;
|
||||||
|
|
||||||
|
public record RoomInfoResponse(Room room) {}
|
||||||
@ -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<Room.Short> rooms) {}
|
||||||
@ -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;
|
import java.util.List;
|
||||||
|
|
||||||
@ -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:@_;]+$");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(":api")
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
|
||||||
|
|||||||
@ -7,11 +7,11 @@ import org.springframework.context.annotation.Profile;
|
|||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
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.interceptor.DebugInterceptor;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||||
import ru.dragonestia.picker.repository.RoomRepository;
|
import ru.dragonestia.picker.repository.RoomRepository;
|
||||||
import ru.dragonestia.picker.repository.NodeRepository;
|
import ru.dragonestia.picker.repository.NodeRepository;
|
||||||
|
|||||||
@ -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<ErrorResponse> create(int code, ApiException ex) {
|
||||||
|
var details = new HashMap<String, String>();
|
||||||
|
ex.appendDetailsToErrorResponse(details);
|
||||||
|
|
||||||
|
return ResponseEntity.status(code).body(new ErrorResponse(ex.getErrorId(), ex.getMessage(), details));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +1,18 @@
|
|||||||
package ru.dragonestia.picker.controller;
|
package ru.dragonestia.picker.controller;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.HttpStatusCode;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import ru.dragonestia.picker.controller.response.NodeDetailsResponse;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.controller.response.NodeListResponse;
|
import ru.dragonestia.picker.api.model.type.PickingMode;
|
||||||
import ru.dragonestia.picker.controller.response.NodeRegisterResponse;
|
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.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.NodeService;
|
||||||
import ru.dragonestia.picker.service.RoomService;
|
import ru.dragonestia.picker.service.RoomService;
|
||||||
import ru.dragonestia.picker.util.NamingValidator;
|
import ru.dragonestia.picker.util.NamingValidator;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/nodes")
|
@RequestMapping("/nodes")
|
||||||
@ -25,47 +21,35 @@ public class NodeController {
|
|||||||
|
|
||||||
private final NodeService nodeService;
|
private final NodeService nodeService;
|
||||||
private final RoomService roomService;
|
private final RoomService roomService;
|
||||||
|
private final NamingValidator namingValidator;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
NodeListResponse allNodes() {
|
NodeListResponse allNodes() {
|
||||||
return new NodeListResponse(nodeService.all());
|
return new NodeListResponse(nodeService.all().stream().map(Node::toResponseObject).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
NodeRegisterResponse registerNode(@RequestParam(name = "nodeId") String nodeId,
|
ResponseEntity<?> registerNode(@RequestParam(name = "nodeId") String nodeId,
|
||||||
@RequestParam(name = "method") PickingMode method) {
|
@RequestParam(name = "method") PickingMode method) {
|
||||||
|
|
||||||
try {
|
|
||||||
nodeService.create(new Node(nodeId, method));
|
nodeService.create(new Node(nodeId, method));
|
||||||
} catch (IllegalArgumentException ex) {
|
return ResponseEntity.ok().build();
|
||||||
return new NodeRegisterResponse(false, ex.getMessage());
|
|
||||||
} catch (Error error) {
|
|
||||||
new NodeRegisterResponse(false, error.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NodeRegisterResponse(true, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{nodeId}")
|
@GetMapping("/{nodeId}")
|
||||||
ResponseEntity<NodeDetailsResponse> nodeDetails(@PathVariable("nodeId") String nodeId) {
|
ResponseEntity<NodeDetailsResponse> nodeDetails(@PathVariable("nodeId") String nodeId) {
|
||||||
if (!NamingValidator.validateNodeId(nodeId)) {
|
namingValidator.validateNodeId(nodeId);
|
||||||
return new ResponseEntity<>(HttpStatusCode.valueOf(404));
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
return nodeService.find(nodeId)
|
||||||
return nodeOpt.map(node -> ResponseEntity.ok(new NodeDetailsResponse(node)))
|
.map(node -> ResponseEntity.ok(new NodeDetailsResponse(node.toResponseObject())))
|
||||||
.orElseGet(() -> new ResponseEntity<>(HttpStatusCode.valueOf(404)));
|
.orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{nodeId}")
|
@DeleteMapping("/{nodeId}")
|
||||||
ResponseEntity<?> removeNode(@PathVariable("nodeId") String nodeId) {
|
ResponseEntity<?> removeNode(@PathVariable("nodeId") String nodeId) {
|
||||||
if (!NamingValidator.validateNodeId(nodeId)) {
|
namingValidator.validateNodeId(nodeId);
|
||||||
return ResponseEntity.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
|
||||||
nodeOpt.ifPresent(nodeService::remove);
|
|
||||||
|
|
||||||
|
nodeService.find(nodeId).ifPresent(nodeService::remove);
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,30 +57,11 @@ public class NodeController {
|
|||||||
ResponseEntity<?> pickRoom(@PathVariable("nodeId") String nodeId,
|
ResponseEntity<?> pickRoom(@PathVariable("nodeId") String nodeId,
|
||||||
@RequestParam(name = "userIds") String userIds) {
|
@RequestParam(name = "userIds") String userIds) {
|
||||||
|
|
||||||
if (!NamingValidator.validateNodeId(nodeId)) {
|
namingValidator.validateNodeId(nodeId);
|
||||||
return new ResponseEntity<>(HttpStatusCode.valueOf(404));
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
if (nodeOpt.isEmpty()) {
|
var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
|
||||||
return new ResponseEntity<>(HttpStatusCode.valueOf(404));
|
var room = roomService.pickAvailable(node, users);
|
||||||
}
|
|
||||||
|
|
||||||
var node = nodeOpt.get();
|
|
||||||
|
|
||||||
var list = new LinkedList<User>();
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(room); // TODO: make other json schema
|
return ResponseEntity.ok(room); // TODO: make other json schema
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,16 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import ru.dragonestia.picker.controller.response.RoomInfoResponse;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.controller.response.RoomListResponse;
|
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
|
||||||
import ru.dragonestia.picker.controller.response.RoomRegisterResponse;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||||
import ru.dragonestia.picker.service.RoomService;
|
import ru.dragonestia.picker.service.RoomService;
|
||||||
import ru.dragonestia.picker.service.NodeService;
|
import ru.dragonestia.picker.service.NodeService;
|
||||||
import ru.dragonestia.picker.util.NamingValidator;
|
import ru.dragonestia.picker.util.NamingValidator;
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/nodes/{nodeId}/rooms")
|
@RequestMapping("/nodes/{nodeId}/rooms")
|
||||||
@ -23,49 +22,40 @@ public class RoomController {
|
|||||||
|
|
||||||
private final NodeService nodeService;
|
private final NodeService nodeService;
|
||||||
private final RoomService roomService;
|
private final RoomService roomService;
|
||||||
|
private final NamingValidator namingValidator;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
ResponseEntity<RoomListResponse> all(@PathVariable(name = "nodeId") String nodeId) {
|
ResponseEntity<RoomListResponse> all(@PathVariable(name = "nodeId") String nodeId) {
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
|
||||||
return nodeOpt.map(node -> ResponseEntity.ok(new RoomListResponse(nodeId,
|
return nodeService.find(nodeId)
|
||||||
|
.map(node -> ResponseEntity.ok(new RoomListResponse(nodeId,
|
||||||
roomService.all(node).stream()
|
roomService.all(node).stream()
|
||||||
.map(room -> new RoomListResponse.RoomDTO(room.getId(), room.getSlots().getSlots(), room.isLocked()))
|
.map(room -> new ru.dragonestia.picker.api.model.Room.Short(room.getId(), room.getSlots().getSlots(), room.isLocked()))
|
||||||
.toList()
|
.toList()
|
||||||
))).orElseGet(() -> ResponseEntity.notFound().build());
|
))).orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
ResponseEntity<RoomRegisterResponse> register(@PathVariable(name = "nodeId") String nodeId,
|
ResponseEntity<?> register(@PathVariable(name = "nodeId") String nodeId,
|
||||||
@RequestParam(name = "roomId") String roomId,
|
@RequestParam(name = "roomId") String roomId,
|
||||||
@RequestParam(name = "slots") int slots,
|
@RequestParam(name = "slots") int slots,
|
||||||
@RequestParam(name = "payload") String payload,
|
@RequestParam(name = "payload") String payload,
|
||||||
@RequestParam(name = "locked", defaultValue = "false") boolean locked) {
|
@RequestParam(name = "locked", defaultValue = "false") boolean locked) {
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
|
var room = Room.create(roomId, node, SlotLimit.of(slots), payload);
|
||||||
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);
|
|
||||||
room.setLocked(locked);
|
room.setLocked(locked);
|
||||||
try {
|
|
||||||
roomService.create(room);
|
roomService.create(room);
|
||||||
return ResponseEntity.ok(new RoomRegisterResponse(true, ""));
|
|
||||||
} catch (Error error) {
|
return ResponseEntity.ok().build();
|
||||||
return ResponseEntity.status(400).body(new RoomRegisterResponse(false, error.getMessage()));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return ResponseEntity.status(500).body(new RoomRegisterResponse(false, ex.getMessage()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{roomId}")
|
@DeleteMapping("/{roomId}")
|
||||||
ResponseEntity<?> remove(@PathVariable("nodeId") String nodeId,
|
ResponseEntity<?> remove(@PathVariable("nodeId") String nodeId,
|
||||||
@PathVariable("roomId") String roomId) {
|
@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);
|
var nodeOpt = nodeService.find(nodeId);
|
||||||
nodeOpt.flatMap(node -> roomService.find(node, roomId))
|
nodeOpt.flatMap(node -> roomService.find(node, roomId))
|
||||||
@ -77,40 +67,26 @@ public class RoomController {
|
|||||||
@GetMapping("/{roomId}")
|
@GetMapping("/{roomId}")
|
||||||
ResponseEntity<RoomInfoResponse> info(@PathVariable("nodeId") String nodeId,
|
ResponseEntity<RoomInfoResponse> info(@PathVariable("nodeId") String nodeId,
|
||||||
@PathVariable("roomId") String roomId) {
|
@PathVariable("roomId") String roomId) {
|
||||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
|
||||||
return ResponseEntity.ok().build();
|
namingValidator.validateNodeId(nodeId);
|
||||||
|
namingValidator.validateRoomId(nodeId, roomId);
|
||||||
|
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
@PutMapping("/{roomId}/lock")
|
||||||
if (nodeOpt.isEmpty()) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
|
||||||
return roomOpt.map(room -> ResponseEntity.ok(new RoomInfoResponse(room)))
|
|
||||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{roomId}/lock")
|
|
||||||
ResponseEntity<Boolean> lockBucket(@PathVariable("nodeId") String nodeId,
|
ResponseEntity<Boolean> lockBucket(@PathVariable("nodeId") String nodeId,
|
||||||
@PathVariable("roomId") String roomId,
|
@PathVariable("roomId") String roomId,
|
||||||
@RequestParam(name = "newState") boolean value) {
|
@RequestParam(name = "newState") boolean value) {
|
||||||
|
|
||||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
namingValidator.validateNodeId(nodeId);
|
||||||
return ResponseEntity.notFound().build();
|
namingValidator.validateRoomId(nodeId, roomId);
|
||||||
}
|
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
if (nodeOpt.isEmpty()) {
|
var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId));
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
|
||||||
if (roomOpt.isEmpty()) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
var room = roomOpt.get();
|
|
||||||
room.setLocked(value);
|
room.setLocked(value);
|
||||||
return ResponseEntity.ok(true);
|
return ResponseEntity.ok(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package ru.dragonestia.picker.controller;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import ru.dragonestia.picker.controller.response.RoomUserListResponse;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.controller.response.LinkUsersWithRoomResponse;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
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.service.UserService;
|
||||||
import ru.dragonestia.picker.util.NamingValidator;
|
import ru.dragonestia.picker.util.NamingValidator;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RestController
|
@RestController
|
||||||
@ -24,21 +25,15 @@ public class UserRoomController {
|
|||||||
private final NodeService nodeService;
|
private final NodeService nodeService;
|
||||||
private final RoomService roomService;
|
private final RoomService roomService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
private final NamingValidator namingValidator;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
ResponseEntity<RoomUserListResponse> usersInsideRoom(@PathVariable(name = "nodeId") String nodeId,
|
ResponseEntity<RoomUserListResponse> usersInsideRoom(@PathVariable(name = "nodeId") String nodeId,
|
||||||
@PathVariable(name = "roomId") String bucketId) {
|
@PathVariable(name = "roomId") String roomId) {
|
||||||
|
|
||||||
Room room;
|
|
||||||
try {
|
|
||||||
var temp = getNodeAndRoom(nodeId, bucketId);
|
|
||||||
room = temp.room();
|
|
||||||
} catch (Error error) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var room = getNodeAndRoom(nodeId, roomId).room();
|
||||||
var users = userService.getRoomUsers(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
|
@PostMapping
|
||||||
@ -47,28 +42,10 @@ public class UserRoomController {
|
|||||||
@RequestParam(name = "userIds") String userIds,
|
@RequestParam(name = "userIds") String userIds,
|
||||||
@RequestParam(name = "force") boolean force) {
|
@RequestParam(name = "force") boolean force) {
|
||||||
|
|
||||||
Room room;
|
var room = getNodeAndRoom(nodeId, roomId).room();
|
||||||
try {
|
var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
|
||||||
var temp = getNodeAndRoom(nodeId, roomId);
|
var usedSlots = userService.linkUsersWithRoom(room, users, force);
|
||||||
room = temp.room();
|
return ResponseEntity.ok(new LinkUsersWithRoomResponse(usedSlots, room.getSlots().getSlots()));
|
||||||
} catch (Error error) {
|
|
||||||
return ResponseEntity.status(404).body(new LinkUsersWithRoomResponse(false, error.getMessage(), -1, -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new LinkedList<User>();
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
@ -76,43 +53,21 @@ public class UserRoomController {
|
|||||||
@PathVariable(name = "roomId") String roomId,
|
@PathVariable(name = "roomId") String roomId,
|
||||||
@RequestParam(name = "userIds") String userIds) {
|
@RequestParam(name = "userIds") String userIds) {
|
||||||
|
|
||||||
Room room;
|
var room = getNodeAndRoom(nodeId, roomId).room();
|
||||||
try {
|
var users = namingValidator.validateUserIds(Arrays.stream(userIds.split(",")).toList());
|
||||||
var temp = getNodeAndRoom(nodeId, roomId);
|
userService.unlinkUsersFromRoom(room, users);
|
||||||
room = temp.room();
|
|
||||||
|
|
||||||
var list = new LinkedList<User>();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record NodeAndRoom(Node node, Room room) {}
|
private record NodeAndRoom(Node node, Room room) {}
|
||||||
|
|
||||||
private NodeAndRoom getNodeAndRoom(String nodeId, String roomId) {
|
private NodeAndRoom getNodeAndRoom(String nodeId, String roomId) {
|
||||||
if (!NamingValidator.validateNodeId(nodeId) || !NamingValidator.validateRoomId(roomId)) {
|
namingValidator.validateNodeId(nodeId);
|
||||||
throw new Error();
|
namingValidator.validateRoomId(nodeId, roomId);
|
||||||
}
|
|
||||||
|
|
||||||
var nodeOpt = nodeService.find(nodeId);
|
var node = nodeService.find(nodeId).orElseThrow(() -> new NodeNotFoundException(nodeId));
|
||||||
if (nodeOpt.isEmpty()) {
|
var room = roomService.find(node, roomId).orElseThrow(() -> new RoomNotFoundException(nodeId, roomId));
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomOpt = roomService.find(Objects.requireNonNull(nodeOpt.get()), roomId);
|
return new NodeAndRoom(node, room);
|
||||||
if (roomOpt.isEmpty()) {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NodeAndRoom(nodeOpt.get(), roomOpt.get());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
public record LinkUsersWithRoomResponse(boolean success, String message, int usedSlots, int totalSlots) {}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
import ru.dragonestia.picker.model.Node;
|
|
||||||
|
|
||||||
public record NodeDetailsResponse(Node node) {}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
import ru.dragonestia.picker.model.Node;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record NodeListResponse(List<Node> nodes) {}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
public record NodeRegisterResponse(boolean success, String message) {}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
import ru.dragonestia.picker.model.Room;
|
|
||||||
|
|
||||||
public record RoomInfoResponse(Room room) {}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record RoomListResponse(String node, List<RoomDTO> rooms) {
|
|
||||||
|
|
||||||
public record RoomDTO(String id, int slots, boolean locked) {}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
package ru.dragonestia.picker.controller.response;
|
|
||||||
|
|
||||||
public record RoomRegisterResponse(boolean success, String message) {}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package ru.dragonestia.picker.model;
|
package ru.dragonestia.picker.model;
|
||||||
|
|
||||||
import lombok.NonNull;
|
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) {
|
public record Node(@NonNull String id, @NonNull PickingMode mode) {
|
||||||
|
|
||||||
@ -19,4 +19,8 @@ public record Node(@NonNull String id, @NonNull PickingMode mode) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ru.dragonestia.picker.api.model.Node toResponseObject() {
|
||||||
|
return new ru.dragonestia.picker.api.model.Node(id, mode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,4 +43,10 @@ public class Room {
|
|||||||
}
|
}
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,4 +18,8 @@ public record User(@NonNull String id) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ru.dragonestia.picker.api.model.User toResponseObject() {
|
||||||
|
return new ru.dragonestia.picker.api.model.User(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
package ru.dragonestia.picker.model.type;
|
|
||||||
|
|
||||||
public enum PickingMode {
|
|
||||||
SEQUENTIAL_FILLING,
|
|
||||||
ROUND_ROBIN,
|
|
||||||
LEAST_PICKED,
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.repository;
|
package ru.dragonestia.picker.repository;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -7,7 +8,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface NodeRepository {
|
public interface NodeRepository {
|
||||||
|
|
||||||
void create(Node node);
|
void create(Node node) throws NodeAlreadyExistException;
|
||||||
|
|
||||||
void delete(Node node);
|
void delete(Node node);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.repository;
|
package ru.dragonestia.picker.repository;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
@ -10,7 +11,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface RoomRepository {
|
public interface RoomRepository {
|
||||||
|
|
||||||
void create(Room room);
|
void create(Room room) throws RoomAlreadyExistException;
|
||||||
|
|
||||||
void remove(Room room);
|
void remove(Room room);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.repository;
|
package ru.dragonestia.picker.repository;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.exception.RoomAreFullException;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
public interface UserRepository {
|
public interface UserRepository {
|
||||||
|
|
||||||
Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force);
|
Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
|
||||||
|
|
||||||
int unlinkWithRoom(Room room, Collection<User> users);
|
int unlinkWithRoom(Room room, Collection<User> users);
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.repository.RoomRepository;
|
import ru.dragonestia.picker.repository.RoomRepository;
|
||||||
import ru.dragonestia.picker.repository.NodeRepository;
|
import ru.dragonestia.picker.repository.NodeRepository;
|
||||||
@ -22,10 +23,10 @@ public class NodeRepositoryImpl implements NodeRepository {
|
|||||||
private final Map<String, Node> nodeMap = new ConcurrentHashMap<>();
|
private final Map<String, Node> nodeMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create(Node node) {
|
public void create(Node node) throws NodeAlreadyExistException {
|
||||||
synchronized (nodeMap) {
|
synchronized (nodeMap) {
|
||||||
if (nodeMap.containsKey(node.id())) {
|
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);
|
nodeMap.put(node.id(), node);
|
||||||
|
|||||||
@ -2,9 +2,9 @@ package ru.dragonestia.picker.repository.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import ru.dragonestia.picker.api.model.type.PickingMode;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.repository.UserRepository;
|
import ru.dragonestia.picker.repository.UserRepository;
|
||||||
import ru.dragonestia.picker.repository.impl.picker.*;
|
import ru.dragonestia.picker.repository.impl.picker.*;
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package ru.dragonestia.picker.repository.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
@ -21,7 +22,7 @@ public class RoomRepositoryImpl implements RoomRepository {
|
|||||||
private final Map<Node, Rooms> node2roomsMap = new ConcurrentHashMap<>();
|
private final Map<Node, Rooms> node2roomsMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create(Room room) {
|
public void create(Room room) throws RoomAlreadyExistException {
|
||||||
var nodeId = room.getNodeId();
|
var nodeId = room.getNodeId();
|
||||||
|
|
||||||
synchronized (node2roomsMap) {
|
synchronized (node2roomsMap) {
|
||||||
@ -35,7 +36,7 @@ public class RoomRepositoryImpl implements RoomRepository {
|
|||||||
|
|
||||||
var rooms = node2roomsMap.get(node.get());
|
var rooms = node2roomsMap.get(node.get());
|
||||||
if (rooms.containsKey(room.getId())) {
|
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)));
|
rooms.put(room.getId(), new RoomContainer(room, new AtomicInteger(0)));
|
||||||
pickerRepository.find(room.getNodeId()).add(room);
|
pickerRepository.find(room.getNodeId()).add(room);
|
||||||
|
|||||||
@ -2,10 +2,9 @@ package ru.dragonestia.picker.repository.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
import ru.dragonestia.picker.api.exception.RoomAreFullException;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
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.UserRepository;
|
||||||
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
|
import ru.dragonestia.picker.repository.impl.cache.NodeId2PickerModeCache;
|
||||||
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
|
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
|
||||||
@ -23,7 +22,7 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
|
private final Map<NodeRoomPath, Set<User>> roomUsers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force) {
|
public Map<User, Boolean> linkWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException {
|
||||||
var result = new HashMap<User, Boolean>();
|
var result = new HashMap<User, Boolean>();
|
||||||
|
|
||||||
synchronized (usersMap) {
|
synchronized (usersMap) {
|
||||||
@ -39,7 +38,7 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (room.getSlots().getSlots() < usersSet.size() + users.size()) {
|
if (room.getSlots().getSlots() < usersSet.size() + users.size()) {
|
||||||
throw new Error("Room are full");
|
throw new RoomAreFullException(room.getNodeId(), room.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package ru.dragonestia.picker.repository.impl.picker;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.repository.UserRepository;
|
import ru.dragonestia.picker.repository.UserRepository;
|
||||||
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
|
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package ru.dragonestia.picker.repository.impl.picker;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
|
|
||||||
public interface RoomPicker extends Picker<Room, User> {
|
public interface RoomPicker extends Picker<Room, User> {
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package ru.dragonestia.picker.repository.impl.picker;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.repository.UserRepository;
|
import ru.dragonestia.picker.repository.UserRepository;
|
||||||
import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
|
import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package ru.dragonestia.picker.repository.impl.picker;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.repository.UserRepository;
|
import ru.dragonestia.picker.repository.UserRepository;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package ru.dragonestia.picker.service;
|
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 ru.dragonestia.picker.model.Node;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -7,7 +9,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface NodeService {
|
public interface NodeService {
|
||||||
|
|
||||||
void create(Node node);
|
void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException;
|
||||||
|
|
||||||
void remove(Node node);
|
void remove(Node node);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package ru.dragonestia.picker.service;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
@ -9,7 +11,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
public interface RoomService {
|
public interface RoomService {
|
||||||
|
|
||||||
void create(Room room);
|
void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException;
|
||||||
|
|
||||||
void remove(Room room);
|
void remove(Room room);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.service;
|
package ru.dragonestia.picker.service;
|
||||||
|
|
||||||
|
import ru.dragonestia.picker.api.exception.RoomAreFullException;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ public interface UserService {
|
|||||||
|
|
||||||
List<Room> getUserRooms(User user);
|
List<Room> getUserRooms(User user);
|
||||||
|
|
||||||
int linkUsersWithRoom(Room room, Collection<User> users, boolean force);
|
int linkUsersWithRoom(Room room, Collection<User> users, boolean force) throws RoomAreFullException;
|
||||||
|
|
||||||
void unlinkUsersFromRoom(Room room, Collection<User> users);
|
void unlinkUsersFromRoom(Room room, Collection<User> users);
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package ru.dragonestia.picker.service.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
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.model.Node;
|
||||||
import ru.dragonestia.picker.repository.NodeRepository;
|
import ru.dragonestia.picker.repository.NodeRepository;
|
||||||
import ru.dragonestia.picker.service.NodeService;
|
import ru.dragonestia.picker.service.NodeService;
|
||||||
@ -15,13 +17,11 @@ import java.util.Optional;
|
|||||||
public class NodeServiceImpl implements NodeService {
|
public class NodeServiceImpl implements NodeService {
|
||||||
|
|
||||||
private final NodeRepository nodeRepository;
|
private final NodeRepository nodeRepository;
|
||||||
|
private final NamingValidator namingValidator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create(Node node) {
|
public void create(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
|
||||||
if (!NamingValidator.validateNodeId(node.id())) {
|
namingValidator.validateNodeId(node.id());
|
||||||
throw new Error("Invalid node id format");
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeRepository.create(node);
|
nodeRepository.create(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package ru.dragonestia.picker.service.impl;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
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.Room;
|
||||||
import ru.dragonestia.picker.model.Node;
|
import ru.dragonestia.picker.model.Node;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
@ -17,13 +19,11 @@ import java.util.Optional;
|
|||||||
public class RoomServiceImpl implements RoomService {
|
public class RoomServiceImpl implements RoomService {
|
||||||
|
|
||||||
private final RoomRepository roomRepository;
|
private final RoomRepository roomRepository;
|
||||||
|
private final NamingValidator namingValidator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create(Room room) {
|
public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException {
|
||||||
if (!NamingValidator.validateRoomId(room.getId())) {
|
namingValidator.validateRoomId(room.getNodeId(), room.getId());
|
||||||
throw new Error("Invalid room id format");
|
|
||||||
}
|
|
||||||
|
|
||||||
roomRepository.create(room);
|
roomRepository.create(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,51 @@
|
|||||||
package ru.dragonestia.picker.util;
|
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 class NamingValidator {
|
||||||
|
|
||||||
public boolean validateNodeId(String input) {
|
public void validateNodeId(String input) throws InvalidNodeIdentifierException {
|
||||||
return input.matches("^[a-z\\d-]+$");
|
if (ValidateIdentifier.forNode(input)) return;
|
||||||
|
|
||||||
|
throw new InvalidNodeIdentifierException(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateRoomId(String input) {
|
public void validateRoomId(String nodeId, String input) throws InvalidRoomIdentifierException {
|
||||||
return input.matches("^[a-z\\d-]+$");
|
if (ValidateIdentifier.forRoom(input)) return;
|
||||||
|
|
||||||
|
throw new InvalidRoomIdentifierException(nodeId, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateUserId(String input) {
|
private boolean validateUserId(String input) {
|
||||||
return input.matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$");
|
return ValidateIdentifier.forUser(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<User> validateUserIds(List<String> input) throws InvalidUsernamesException {
|
||||||
|
var users = new LinkedList<User>();
|
||||||
|
var invalid = new LinkedList<String>();
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,10 @@ package ru.dragonestia.picker.config;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.TestConfiguration;
|
import org.springframework.boot.test.context.TestConfiguration;
|
||||||
import org.springframework.context.annotation.Bean;
|
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.Node;
|
||||||
import ru.dragonestia.picker.model.Room;
|
import ru.dragonestia.picker.model.Room;
|
||||||
import ru.dragonestia.picker.model.User;
|
import ru.dragonestia.picker.model.User;
|
||||||
import ru.dragonestia.picker.model.type.PickingMode;
|
|
||||||
import ru.dragonestia.picker.model.type.SlotLimit;
|
import ru.dragonestia.picker.model.type.SlotLimit;
|
||||||
import ru.dragonestia.picker.repository.NodeRepository;
|
import ru.dragonestia.picker.repository.NodeRepository;
|
||||||
import ru.dragonestia.picker.repository.RoomRepository;
|
import ru.dragonestia.picker.repository.RoomRepository;
|
||||||
|
|||||||
@ -23,8 +23,11 @@ ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(":api")
|
||||||
|
|
||||||
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
implementation 'com.vaadin:vaadin-spring-boot-starter'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,18 +9,15 @@ import com.vaadin.flow.component.html.Div;
|
|||||||
import com.vaadin.flow.component.html.H2;
|
import com.vaadin.flow.component.html.H2;
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
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.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import ru.dragonestia.picker.api.model.Room;
|
||||||
import ru.dragonestia.picker.cp.model.User;
|
import ru.dragonestia.picker.api.model.User;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class AddUsers extends Details {
|
public class AddUsers extends Details {
|
||||||
|
|
||||||
@ -79,8 +76,7 @@ public class AddUsers extends Details {
|
|||||||
try {
|
try {
|
||||||
onCommit.accept(readAllUsers(), ignoreSlots.getValue());
|
onCommit.accept(readAllUsers(), ignoreSlots.getValue());
|
||||||
} catch (Error error) {
|
} catch (Error error) {
|
||||||
Notification.show(error.getMessage(), 3000, Notification.Position.TOP_END)
|
Notifications.error(error.getMessage());
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
|||||||
|
|
||||||
public class NavPath extends HorizontalLayout{
|
public class NavPath extends HorizontalLayout{
|
||||||
|
|
||||||
public NavPath(Point root, Point... points) {
|
private NavPath(Point root, Point... points) {
|
||||||
setWidth("100%");
|
setWidth("100%");
|
||||||
setAlignItems(Alignment.CENTER);
|
setAlignItems(Alignment.CENTER);
|
||||||
getStyle().set("background-color", "#F3F3F3")
|
getStyle().set("background-color", "#F3F3F3")
|
||||||
@ -58,5 +58,20 @@ public class NavPath extends HorizontalLayout{
|
|||||||
return button;
|
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) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.cp.component;
|
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.Button;
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
import com.vaadin.flow.component.dialog.Dialog;
|
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.html.Paragraph;
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
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.HorizontalLayout;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import ru.dragonestia.picker.api.model.Node;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -51,14 +50,14 @@ public class NodeList extends VerticalLayout {
|
|||||||
var temp = input.trim();
|
var temp = input.trim();
|
||||||
|
|
||||||
nodesGrid.setItems(cachedNodes.stream()
|
nodesGrid.setItems(cachedNodes.stream()
|
||||||
.filter(node -> node.id().startsWith(temp))
|
.filter(node -> node.getId().startsWith(temp))
|
||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid<Node> createGrid() {
|
private Grid<Node> createGrid() {
|
||||||
var grid = new Grid<>(Node.class, false);
|
var grid = new Grid<>(Node.class, false);
|
||||||
grid.addColumn(Node::id).setHeader("Identifier");
|
grid.addColumn(Node::getId).setHeader("Identifier");
|
||||||
grid.addColumn(node -> node.mode().getName()).setHeader("Mode");
|
grid.addColumn(node -> node.getMode().getName()).setHeader("Mode");
|
||||||
grid.addComponentColumn(this::createManageButtons).setHeader("Manage");
|
grid.addComponentColumn(this::createManageButtons).setHeader("Manage");
|
||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
@ -84,29 +83,28 @@ public class NodeList extends VerticalLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void clickDetailsButton(Node node) {
|
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) {
|
private void clickRemoveButton(Node node) {
|
||||||
var dialog = new Dialog("Confirm node deletion");
|
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("<p>Confirm that you want to delete node. Enter <b><u>" + node.getId() + "</u></b> to field below and confirm.</p>"));
|
||||||
|
|
||||||
var inputField = new TextField();
|
var inputField = new TextField();
|
||||||
|
inputField.setWidth("100%");
|
||||||
dialog.add(inputField);
|
dialog.add(inputField);
|
||||||
|
|
||||||
{ // confirm
|
{ // confirm
|
||||||
var button = new Button("Confirm");
|
var button = new Button("Confirm");
|
||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
if (!node.id().equals(inputField.getValue())) {
|
if (!node.getId().equals(inputField.getValue())) {
|
||||||
Notification.show("Invalid input", 3000, Notification.Position.TOP_END)
|
Notifications.error("Invalid input");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeNode(node);
|
removeNode(node);
|
||||||
Notification.show("Node '" + node.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
Notifications.success("Node <b>" + node.getId() + "</b> was successfully removed!");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -129,7 +127,7 @@ public class NodeList extends VerticalLayout {
|
|||||||
|
|
||||||
private void removeNode(Node node) {
|
private void removeNode(Node node) {
|
||||||
if (removeMethod != null) {
|
if (removeMethod != null) {
|
||||||
removeMethod.accept(node.id());
|
removeMethod.accept(node.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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("<span>" + text + "</span>"));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,8 +7,6 @@ import com.vaadin.flow.component.button.ButtonVariant;
|
|||||||
import com.vaadin.flow.component.details.Details;
|
import com.vaadin.flow.component.details.Details;
|
||||||
import com.vaadin.flow.component.html.H2;
|
import com.vaadin.flow.component.html.H2;
|
||||||
import com.vaadin.flow.component.html.Span;
|
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.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
|
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
|
||||||
import com.vaadin.flow.component.radiobutton.RadioGroupVariant;
|
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.component.textfield.TextField;
|
||||||
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
import com.vaadin.flow.data.renderer.ComponentRenderer;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import ru.dragonestia.picker.api.model.Node;
|
||||||
import ru.dragonestia.picker.cp.model.type.PickingMode;
|
import ru.dragonestia.picker.api.model.type.PickingMode;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@ -91,8 +89,7 @@ public class RegisterNode extends Details {
|
|||||||
error = "Invalid node id format";
|
error = "Invalid node id format";
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.show(error, 3000, Notification.Position.TOP_END)
|
Notifications.error(error);
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,13 +97,11 @@ public class RegisterNode extends Details {
|
|||||||
var response = onSubmit.apply(node);
|
var response = onSubmit.apply(node);
|
||||||
clear();
|
clear();
|
||||||
if (response.error()) {
|
if (response.error()) {
|
||||||
Notification.show(response.reason(), 3000, Notification.Position.TOP_END)
|
Notifications.error(response.reason());
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.show("Node was successfully registered", 3000, Notification.Position.TOP_END)
|
Notifications.success("Node was successfully registered");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Response(boolean error, @Nullable String reason) {}
|
public record Response(boolean error, @Nullable String reason) {}
|
||||||
|
|||||||
@ -6,16 +6,13 @@ import com.vaadin.flow.component.button.ButtonVariant;
|
|||||||
import com.vaadin.flow.component.checkbox.Checkbox;
|
import com.vaadin.flow.component.checkbox.Checkbox;
|
||||||
import com.vaadin.flow.component.details.Details;
|
import com.vaadin.flow.component.details.Details;
|
||||||
import com.vaadin.flow.component.html.H2;
|
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.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.Autocomplete;
|
import com.vaadin.flow.component.textfield.Autocomplete;
|
||||||
import com.vaadin.flow.component.textfield.TextArea;
|
import com.vaadin.flow.component.textfield.TextArea;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import ru.dragonestia.picker.api.model.Node;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import ru.dragonestia.picker.api.model.Room;
|
||||||
import ru.dragonestia.picker.cp.model.type.SlotLimit;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@ -45,7 +42,7 @@ public class RegisterRoom extends Details {
|
|||||||
private TextField createNodeIdentifierField() {
|
private TextField createNodeIdentifierField() {
|
||||||
var field = new TextField("Node identifier");
|
var field = new TextField("Node identifier");
|
||||||
field.setMinWidth(20, Unit.REM);
|
field.setMinWidth(20, Unit.REM);
|
||||||
field.setValue(node.id());
|
field.setValue(node.getId());
|
||||||
field.setReadOnly(true);
|
field.setReadOnly(true);
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
@ -106,23 +103,20 @@ public class RegisterRoom extends Details {
|
|||||||
error = "Invalid room id format";
|
error = "Invalid room id format";
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.show(error, 3000, Notification.Position.TOP_END)
|
Notifications.error(error);
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
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());
|
room.setLocked(lockedField.getValue());
|
||||||
var response = onSubmit.apply(room);
|
var response = onSubmit.apply(room);
|
||||||
clear();
|
clear();
|
||||||
if (response.error()) {
|
if (response.error()) {
|
||||||
Notification.show(response.reason(), 3000, Notification.Position.TOP_END)
|
Notifications.error(response.reason());
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification.show("Room was successfully registered", 3000, Notification.Position.TOP_END)
|
Notifications.success("Room was successfully registered");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Response(boolean error, @Nullable String reason) {}
|
public record Response(boolean error, @Nullable String reason) {}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.dragonestia.picker.cp.component;
|
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.Button;
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
import com.vaadin.flow.component.dialog.Dialog;
|
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.html.Span;
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
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.HorizontalLayout;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.TextField;
|
import com.vaadin.flow.component.textfield.TextField;
|
||||||
import lombok.Setter;
|
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.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -24,12 +23,12 @@ import java.util.function.Consumer;
|
|||||||
public class RoomList extends VerticalLayout {
|
public class RoomList extends VerticalLayout {
|
||||||
|
|
||||||
private final String nodeIdentifier;
|
private final String nodeIdentifier;
|
||||||
private final Grid<RoomDTO> roomsGrid;
|
private final Grid<Room.Short> roomsGrid;
|
||||||
private final TextField searchField;
|
private final TextField searchField;
|
||||||
private List<RoomDTO> cachedRooms;
|
private List<Room.Short> cachedRooms;
|
||||||
@Setter private Consumer<RoomDTO> removeMethod;
|
@Setter private Consumer<Room.Short> removeMethod;
|
||||||
|
|
||||||
public RoomList(String nodeIdentifier, List<RoomDTO> buckets) {
|
public RoomList(String nodeIdentifier, List<Room.Short> buckets) {
|
||||||
this.nodeIdentifier = nodeIdentifier;
|
this.nodeIdentifier = nodeIdentifier;
|
||||||
cachedRooms = buckets;
|
cachedRooms = buckets;
|
||||||
|
|
||||||
@ -57,9 +56,9 @@ public class RoomList extends VerticalLayout {
|
|||||||
.toList());
|
.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Grid<RoomDTO> createGrid() {
|
private Grid<Room.Short> createGrid() {
|
||||||
var grid = new Grid<>(RoomDTO.class, false);
|
var grid = new Grid<>(Room.Short.class, false);
|
||||||
grid.addColumn(RoomDTO::id).setHeader("Identifier");
|
grid.addColumn(Room.Short::id).setHeader("Identifier");
|
||||||
grid.addComponentColumn(room -> {
|
grid.addComponentColumn(room -> {
|
||||||
var result = new Span();
|
var result = new Span();
|
||||||
if (room.slots() == -1) {
|
if (room.slots() == -1) {
|
||||||
@ -84,7 +83,7 @@ public class RoomList extends VerticalLayout {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HorizontalLayout createManageButtons(RoomDTO room) {
|
private HorizontalLayout createManageButtons(Room.Short room) {
|
||||||
var layout = new HorizontalLayout();
|
var layout = new HorizontalLayout();
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -104,18 +103,19 @@ public class RoomList extends VerticalLayout {
|
|||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickDetailsButton(RoomDTO bucket) {
|
private void clickDetailsButton(Room.Short bucket) {
|
||||||
getUI().ifPresent(ui -> {
|
getUI().ifPresent(ui -> {
|
||||||
ui.navigate("/nodes/" + nodeIdentifier +
|
ui.navigate("/nodes/" + nodeIdentifier +
|
||||||
"/rooms/" + bucket.id());
|
"/rooms/" + bucket.id());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clickRemoveButton(RoomDTO bucket) {
|
private void clickRemoveButton(Room.Short bucket) {
|
||||||
var dialog = new Dialog("Confirm bucket deletion");
|
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("<p>Confirm that you want to delete bucket. Enter <b><u>" + bucket.id() + "</u></b> to field below and confirm.</p>"));
|
||||||
|
|
||||||
var inputField = new TextField();
|
var inputField = new TextField();
|
||||||
|
inputField.setWidth("100%");
|
||||||
dialog.add(inputField);
|
dialog.add(inputField);
|
||||||
|
|
||||||
{ // confirm
|
{ // confirm
|
||||||
@ -123,14 +123,12 @@ public class RoomList extends VerticalLayout {
|
|||||||
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
|
||||||
button.addClickListener(event -> {
|
button.addClickListener(event -> {
|
||||||
if (!bucket.id().equals(inputField.getValue())) {
|
if (!bucket.id().equals(inputField.getValue())) {
|
||||||
Notification.show("Invalid input", 3000, Notification.Position.TOP_END)
|
Notifications.error("Invalid input");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBucket(bucket);
|
removeBucket(bucket);
|
||||||
Notification.show("Bucket '" + bucket.id() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
|
Notifications.success("Bucket <b>" + bucket.id() + "</b> was successfully removed!");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,12 +144,12 @@ public class RoomList extends VerticalLayout {
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(List<RoomDTO> buckets) {
|
public void update(List<Room.Short> buckets) {
|
||||||
cachedRooms = buckets;
|
cachedRooms = buckets;
|
||||||
applySearch(searchField.getValue());
|
applySearch(searchField.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeBucket(RoomDTO bucket) {
|
private void removeBucket(Room.Short bucket) {
|
||||||
if (removeMethod != null) {
|
if (removeMethod != null) {
|
||||||
removeMethod.accept(bucket);
|
removeMethod.accept(bucket);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,8 @@ import com.vaadin.flow.component.grid.ColumnTextAlign;
|
|||||||
import com.vaadin.flow.component.grid.Grid;
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
import com.vaadin.flow.component.html.Span;
|
import com.vaadin.flow.component.html.Span;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import ru.dragonestia.picker.api.model.Room;
|
||||||
import ru.dragonestia.picker.cp.model.User;
|
import ru.dragonestia.picker.api.model.User;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -28,7 +28,7 @@ public class UserList extends VerticalLayout {
|
|||||||
|
|
||||||
private Grid<User> createUsersGrid() {
|
private Grid<User> createUsersGrid() {
|
||||||
var grid = new Grid<User>();
|
var grid = new Grid<User>();
|
||||||
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
|
grid.addColumn(user -> 0).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with rooms") // TODO
|
||||||
.setFooter(occupancy);
|
.setFooter(occupancy);
|
||||||
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
|
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
|
||||||
@ -39,6 +39,12 @@ public class UserList extends VerticalLayout {
|
|||||||
cachedUsers = users;
|
cachedUsers = users;
|
||||||
usersGrid.setItems(users);
|
usersGrid.setItems(users);
|
||||||
totalUsers.setText("Total users: " + users.size());
|
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) + "%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public class RestApiConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Supplier<RestTemplate> restTemplate(@Autowired RestTemplateBuilder builder) {
|
Supplier<RestTemplate> restTemplateSupplier(@Autowired RestTemplateBuilder builder) {
|
||||||
return builder::build;
|
return builder::build;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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) + "%";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,10 +3,11 @@ package ru.dragonestia.picker.cp.page;
|
|||||||
import com.vaadin.flow.component.html.H1;
|
import com.vaadin.flow.component.html.H1;
|
||||||
import com.vaadin.flow.component.html.Paragraph;
|
import com.vaadin.flow.component.html.Paragraph;
|
||||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
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("/")
|
@Route("/")
|
||||||
public class HomePage extends VerticalLayout {
|
public class HomePage extends VerticalLayout implements BeforeEnterObserver {
|
||||||
|
|
||||||
public HomePage() {
|
public HomePage() {
|
||||||
super();
|
super();
|
||||||
@ -14,4 +15,9 @@ public class HomePage extends VerticalLayout {
|
|||||||
add(new H1("Hello world!"));
|
add(new H1("Hello world!"));
|
||||||
add(new Paragraph("Hello world!"));
|
add(new Paragraph("Hello world!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
|
||||||
|
throw new NodeNotFoundException("gdfsg");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,72 +4,54 @@ import com.vaadin.flow.component.Html;
|
|||||||
import com.vaadin.flow.component.html.H2;
|
import com.vaadin.flow.component.html.H2;
|
||||||
import com.vaadin.flow.component.html.Hr;
|
import com.vaadin.flow.component.html.Hr;
|
||||||
import com.vaadin.flow.component.html.Paragraph;
|
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.component.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.RoomList;
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
import ru.dragonestia.picker.cp.component.NavPath;
|
||||||
import ru.dragonestia.picker.cp.component.RegisterRoom;
|
import ru.dragonestia.picker.cp.component.RegisterRoom;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
||||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
|
||||||
import ru.dragonestia.picker.cp.repository.RoomRepository;
|
|
||||||
import ru.dragonestia.picker.cp.repository.NodeRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
@PageTitle("Rooms")
|
@PageTitle("Rooms")
|
||||||
@Route("/nodes/:nodeId")
|
@Route("/nodes/:nodeId")
|
||||||
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||||
|
|
||||||
private final NodeRepository nodeRepository;
|
private final NodeRepository nodeRepository;
|
||||||
private final RoomRepository roomRepository;
|
private final RoomRepository roomRepository;
|
||||||
|
private final RouteParamsExtractor paramsExtractor;
|
||||||
|
|
||||||
private Node node;
|
private Node node;
|
||||||
private RegisterRoom registerRoom;
|
private RegisterRoom registerRoom;
|
||||||
private RoomList roomList;
|
private RoomList roomList;
|
||||||
|
|
||||||
public NodeDetailsPage(@Autowired NodeRepository nodeRepository,
|
|
||||||
@Autowired RoomRepository roomRepository) {
|
|
||||||
|
|
||||||
this.nodeRepository = nodeRepository;
|
|
||||||
this.roomRepository = roomRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
public void beforeEnter(BeforeEnterEvent event) {
|
||||||
var nodeIdOpt = event.getRouteParameters().get("nodeId");
|
node = paramsExtractor.extractNodeId(event);
|
||||||
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();
|
|
||||||
|
|
||||||
initComponents(node, roomRepository.all(node));
|
initComponents(node, roomRepository.all(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents(Node node, List<RoomDTO> rooms) {
|
private void initComponents(Node node, List<Room.Short> rooms) {
|
||||||
|
add(NavPath.toNode(node.getId()));
|
||||||
printNodeDetails(node);
|
printNodeDetails(node);
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
add(registerRoom = new RegisterRoom(node, (bucket) -> {
|
add(registerRoom = new RegisterRoom(node, (room) -> {
|
||||||
try {
|
try {
|
||||||
roomRepository.register(bucket);
|
roomRepository.register(room);
|
||||||
return new RegisterRoom.Response(false, null);
|
return new RegisterRoom.Response(false, null);
|
||||||
} catch (Error error) {
|
} catch (Error error) {
|
||||||
return new RegisterRoom.Response(true, error.getMessage());
|
return new RegisterRoom.Response(true, error.getMessage());
|
||||||
@ -78,9 +60,9 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
add(roomList = new RoomList(node.id(), rooms));
|
add(roomList = new RoomList(node.getId(), rooms));
|
||||||
roomList.setRemoveMethod(bucket -> {
|
roomList.setRemoveMethod(room -> {
|
||||||
roomRepository.remove(node, bucket);
|
roomRepository.remove(node, room);
|
||||||
roomList.update(roomRepository.all(node));
|
roomList.update(roomRepository.all(node));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -89,8 +71,8 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
add(new H2("Node details"));
|
add(new H2("Node details"));
|
||||||
|
|
||||||
var layout = new VerticalLayout();
|
var layout = new VerticalLayout();
|
||||||
layout.add(new Html("<span>Identifier: <b>" + node.id() + "</b></span>"));
|
layout.add(new Html("<span>Identifier: <b>" + node.getId() + "</b></span>"));
|
||||||
layout.add(new Html("<span>Mode: <b>" + node.mode().getName() + "</b></span>"));
|
layout.add(new Html("<span>Mode: <b>" + node.getMode().getName() + "</b></span>"));
|
||||||
|
|
||||||
add(layout);
|
add(layout);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,11 @@ import com.vaadin.flow.router.Route;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.NavPath;
|
||||||
import ru.dragonestia.picker.cp.component.NodeList;
|
import ru.dragonestia.picker.cp.component.NodeList;
|
||||||
import ru.dragonestia.picker.cp.component.RegisterNode;
|
import ru.dragonestia.picker.cp.component.RegisterNode;
|
||||||
import ru.dragonestia.picker.cp.repository.NodeRepository;
|
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
@ -26,7 +27,7 @@ public class NodesPage extends VerticalLayout {
|
|||||||
super();
|
super();
|
||||||
this.nodeRepository = nodeRepository;
|
this.nodeRepository = nodeRepository;
|
||||||
|
|
||||||
add(new NavPath(new NavPath.Point("Nodes", "/nodes")));
|
add(NavPath.rootNodes());
|
||||||
add(registerNode = createRegisterNodeElement());
|
add(registerNode = createRegisterNodeElement());
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
add(nodeList = createNodeListElement());
|
add(nodeList = createNodeListElement());
|
||||||
@ -41,10 +42,7 @@ public class NodesPage extends VerticalLayout {
|
|||||||
try {
|
try {
|
||||||
nodeRepository.register(node);
|
nodeRepository.register(node);
|
||||||
return new RegisterNode.Response(false, "");
|
return new RegisterNode.Response(false, "");
|
||||||
} catch (Error ex) {
|
} catch (ApiException ex) {
|
||||||
return new RegisterNode.Response(true, ex.getMessage());
|
|
||||||
} catch (RuntimeException ex) {
|
|
||||||
log.throwing(ex);
|
|
||||||
return new RegisterNode.Response(true, ex.getMessage());
|
return new RegisterNode.Response(true, ex.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
nodeList.update(nodeRepository.all());
|
nodeList.update(nodeRepository.all());
|
||||||
|
|||||||
@ -8,28 +8,30 @@ import com.vaadin.flow.component.html.Hr;
|
|||||||
import com.vaadin.flow.component.html.Paragraph;
|
import com.vaadin.flow.component.html.Paragraph;
|
||||||
import com.vaadin.flow.component.icon.Icon;
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
import com.vaadin.flow.component.icon.VaadinIcon;
|
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.orderedlayout.VerticalLayout;
|
||||||
import com.vaadin.flow.component.textfield.TextArea;
|
import com.vaadin.flow.component.textfield.TextArea;
|
||||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||||
import com.vaadin.flow.router.PageTitle;
|
import com.vaadin.flow.router.PageTitle;
|
||||||
import com.vaadin.flow.router.Route;
|
import com.vaadin.flow.router.Route;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.AddUsers;
|
||||||
import ru.dragonestia.picker.cp.component.NavPath;
|
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.component.UserList;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import ru.dragonestia.picker.cp.util.RouteParamsExtractor;
|
||||||
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 java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
@PageTitle("Room details")
|
@PageTitle("Room details")
|
||||||
@Route("/nodes/:nodeId/rooms/:roomId")
|
@Route("/nodes/:nodeId/rooms/:roomId")
|
||||||
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||||
@ -37,6 +39,8 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
private final NodeRepository nodeRepository;
|
private final NodeRepository nodeRepository;
|
||||||
private final RoomRepository roomRepository;
|
private final RoomRepository roomRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final RouteParamsExtractor paramsExtractor;
|
||||||
|
|
||||||
private Node node;
|
private Node node;
|
||||||
private Room room;
|
private Room room;
|
||||||
private AddUsers addUsers;
|
private AddUsers addUsers;
|
||||||
@ -44,57 +48,16 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
private Button lockRoomButton;
|
private Button lockRoomButton;
|
||||||
private VerticalLayout roomInfo;
|
private VerticalLayout roomInfo;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public RoomDetailsPage(NodeRepository nodeRepository, RoomRepository roomRepository, UserRepository userRepository) {
|
|
||||||
this.nodeRepository = nodeRepository;
|
|
||||||
this.roomRepository = roomRepository;
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeEnter(BeforeEnterEvent event) {
|
public void beforeEnter(BeforeEnterEvent event) {
|
||||||
var nodeIdOpt = event.getRouteParameters().get("nodeId");
|
node = paramsExtractor.extractNodeId(event);
|
||||||
if (nodeIdOpt.isEmpty()) {
|
room = paramsExtractor.extractRoomId(event, node);
|
||||||
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();
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
add(NavPath.toRoom(node.getId(), room.getId()));
|
||||||
add(new H2("Room details"));
|
add(new H2("Room details"));
|
||||||
printRoomDetails();
|
printRoomDetails();
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
@ -108,7 +71,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
roomInfo.removeAll();
|
roomInfo.removeAll();
|
||||||
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getNodeId() + "</b></span>"));
|
roomInfo.add(new Html("<span>Node identifier: <b>" + room.getNodeId() + "</b></span>"));
|
||||||
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getId() + "</b></span>"));
|
roomInfo.add(new Html("<span>Room identifier: <b>" + room.getId() + "</b></span>"));
|
||||||
roomInfo.add(new Html("<span>Slots: <b>" + (room.getSlots().isUnlimited()? "Unlimited" : room.getSlots().slots()) + "</b></span>"));
|
roomInfo.add(new Html("<span>Slots: <b>" + (room.isUnlimited()? "Unlimited" : room.getSlots()) + "</b></span>"));
|
||||||
roomInfo.add(new Html("<span>Locked: <b>" + (room.isLocked()? "Yes" : "No") + "</b></span>"));
|
roomInfo.add(new Html("<span>Locked: <b>" + (room.isLocked()? "Yes" : "No") + "</b></span>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,20 +102,13 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
|
|
||||||
private void changeBucketLockedState() {
|
private void changeBucketLockedState() {
|
||||||
var newValue = !room.isLocked();
|
var newValue = !room.isLocked();
|
||||||
try {
|
|
||||||
roomRepository.lock(room, newValue);
|
roomRepository.lock(room, newValue);
|
||||||
} catch (Error error) {
|
|
||||||
Notification.show(error.getMessage(), 3000, Notification.Position.TOP_END)
|
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
room.setLocked(newValue);
|
room.setLocked(newValue);
|
||||||
setLockRoomButtonState();
|
setLockRoomButtonState();
|
||||||
updateRoomInfo();
|
updateRoomInfo();
|
||||||
|
|
||||||
Notification.show("Success", 3000, Notification.Position.TOP_END)
|
Notifications.success("Success");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void appendUsers(Room room, Collection<User> users, boolean ignoreLimitation) {
|
private void appendUsers(Room room, Collection<User> users, boolean ignoreLimitation) {
|
||||||
@ -160,7 +116,7 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
|
|
||||||
var newUsers = users.stream()
|
var newUsers = users.stream()
|
||||||
.filter(user -> {
|
.filter(user -> {
|
||||||
if (user.id().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
|
if (user.getId().matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,15 +129,12 @@ public class RoomDetailsPage extends VerticalLayout implements BeforeEnterObserv
|
|||||||
|
|
||||||
if (validationFail.get()) {
|
if (validationFail.get()) {
|
||||||
if (newUsers.isEmpty()) {
|
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)
|
Notifications.error("All users entered were added because they do not comply with the rule for writing the user identifier");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
|
||||||
} else {
|
} 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)
|
Notifications.warn("Not all users entered were added because they do not comply with the rule for writing the user identifier");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_WARNING);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Notification.show("Success", 3000, Notification.Position.TOP_END)
|
Notifications.success("Success");
|
||||||
.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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("<p>" + description + "</p>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<InvalidNodeIdentifierException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<InvalidNodeIdentifierException> errorParameter) {
|
||||||
|
var ex = errorParameter.getException();
|
||||||
|
var nodeId = ex.getNodeId();
|
||||||
|
|
||||||
|
init(NavPath.toNode(nodeId), "Error 400", ex.getMessage());
|
||||||
|
return HttpServletResponse.SC_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<InvalidRoomIdentifierException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent event, ErrorParameter<InvalidRoomIdentifierException> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<NodeNotFoundException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<NodeNotFoundException> errorParameter) {
|
||||||
|
var ex = errorParameter.getException();
|
||||||
|
var nodeId = ex.getNodeId();
|
||||||
|
|
||||||
|
init(NavPath.toNode(nodeId), "Error 404", ex.getMessage());
|
||||||
|
return HttpServletResponse.SC_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<RoomNotFoundException> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int setErrorParameter(BeforeEnterEvent beforeEnterEvent, ErrorParameter<RoomNotFoundException> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<Node> all();
|
|
||||||
|
|
||||||
Optional<Node> find(String nodeId);
|
|
||||||
|
|
||||||
void remove(String nodeId);
|
|
||||||
}
|
|
||||||
@ -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<RoomDTO> all(Node node);
|
|
||||||
|
|
||||||
void register(Room room);
|
|
||||||
|
|
||||||
void remove(Room room);
|
|
||||||
|
|
||||||
void remove(Node node, RoomDTO bucket);
|
|
||||||
|
|
||||||
Optional<Room> find(Node node, String roomId);
|
|
||||||
|
|
||||||
void lock(Room room, boolean value);
|
|
||||||
}
|
|
||||||
@ -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<User> users, boolean force);
|
|
||||||
|
|
||||||
void unlinkFromRoom(Room room, Collection<User> users);
|
|
||||||
|
|
||||||
List<User> all(Room room);
|
|
||||||
}
|
|
||||||
@ -3,13 +3,15 @@ package ru.dragonestia.picker.cp.repository.impl;
|
|||||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import org.springframework.http.HttpMethod;
|
||||||
import ru.dragonestia.picker.cp.repository.NodeRepository;
|
import ru.dragonestia.picker.api.exception.InvalidNodeIdentifierException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.NodeDetailsResponse;
|
import ru.dragonestia.picker.api.exception.NodeAlreadyExistException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.NodeListResponse;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.NodeRegisterResponse;
|
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.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -21,41 +23,30 @@ public class NodeRepositoryImpl implements NodeRepository {
|
|||||||
private final RestUtil rest;
|
private final RestUtil rest;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void register(Node node) {
|
public void register(Node node) throws InvalidNodeIdentifierException, NodeAlreadyExistException {
|
||||||
NodeRegisterResponse response;
|
rest.query("nodes", HttpMethod.POST, params -> {
|
||||||
try {
|
params.put("nodeId", node.getId());
|
||||||
response = rest.post(URI.create("nodes"),
|
params.put("method", node.getMode().name());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Node> all() {
|
public List<Node> all() {
|
||||||
return rest.get(URI.create("nodes"), NodeListResponse.class).nodes();
|
return rest.query("nodes", HttpMethod.GET, NodeListResponse.class, params -> {}).nodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Node> find(String nodeId) {
|
public Optional<Node> find(String nodeId) {
|
||||||
try {
|
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());
|
return Optional.of(response.node());
|
||||||
} catch (Exception ex) {
|
} catch (NodeNotFoundException ex) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(String nodeId) {
|
public void remove(String nodeId) {
|
||||||
rest.delete(URI.create("nodes/" + nodeId), params -> {});
|
rest.query("nodes/" + nodeId, HttpMethod.DELETE, params -> {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
package ru.dragonestia.picker.cp.repository.impl;
|
package ru.dragonestia.picker.cp.repository.impl;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
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.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
@ -18,67 +18,41 @@ import java.util.function.Supplier;
|
|||||||
public class RestUtil {
|
public class RestUtil {
|
||||||
|
|
||||||
private final URI serverUrl;
|
private final URI serverUrl;
|
||||||
private final Supplier<RestTemplate> restTemplate;
|
private final Supplier<RestTemplate> restTemplateSupplier;
|
||||||
|
|
||||||
public <T> T get(URI uri, Class<T> responseType) {
|
public void query(String uri, HttpMethod method) {
|
||||||
var template = restTemplate.get();
|
query(uri, method, ParamsConsumer.NONE);
|
||||||
return Objects.requireNonNull(template.getForObject(serverUrl.resolve(uri), responseType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> ResponseEntity<T> getEntity(URI uri, Class<T> responseType) {
|
public void query(String uri, HttpMethod method, ParamsConsumer paramsConsumer) {
|
||||||
var template = restTemplate.get();
|
|
||||||
return template.getForEntity(serverUrl.resolve(uri), responseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T get(URI uri, Class<T> responseType, Consumer<Map<String, String>> paramsConsumer) {
|
|
||||||
var params = new HashMap<String, String>();
|
var params = new HashMap<String, String>();
|
||||||
paramsConsumer.accept(params);
|
paramsConsumer.accept(params);
|
||||||
|
|
||||||
var template = restTemplate.get();
|
var template = restTemplateSupplier.get();
|
||||||
return Objects.requireNonNull(template.getForObject(buildPath(uri, params.keySet()),
|
try {
|
||||||
responseType,
|
template.exchange(buildPath(uri, params.keySet()), method, null, String.class, params);
|
||||||
params));
|
} catch (HttpClientErrorException ex) {
|
||||||
|
throw ExceptionFactory.of(Objects.requireNonNull(ex.getResponseBodyAs(ErrorResponse.class)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T post(URI uri, Class<T> responseType, Consumer<Map<String, String>> paramsConsumer) {
|
public <T> T query(String uri, HttpMethod method, Class<T> clazz) {
|
||||||
|
return query(uri, method, clazz, ParamsConsumer.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T query(String uri, HttpMethod method, Class<T> clazz, ParamsConsumer paramsConsumer) {
|
||||||
var params = new HashMap<String, String>();
|
var params = new HashMap<String, String>();
|
||||||
paramsConsumer.accept(params);
|
paramsConsumer.accept(params);
|
||||||
|
|
||||||
var template = restTemplate.get();
|
var template = restTemplateSupplier.get();
|
||||||
return Objects.requireNonNull(template.postForObject(buildPath(uri, params.keySet()),
|
try {
|
||||||
null,
|
return template.exchange(buildPath(uri, params.keySet()), method, null, clazz, params).getBody();
|
||||||
responseType,
|
} catch (HttpClientErrorException ex) {
|
||||||
params));
|
throw ExceptionFactory.of(Objects.requireNonNull(ex.getResponseBodyAs(ErrorResponse.class)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> ResponseEntity<T> postEntity(URI uri, Class<T> responseType, Consumer<Map<String, String>> paramsConsumer) {
|
private String buildPath(String uri, Collection<String> paramKeys) {
|
||||||
var params = new HashMap<String, String>();
|
|
||||||
paramsConsumer.accept(params);
|
|
||||||
|
|
||||||
var template = restTemplate.get();
|
|
||||||
return template.postForEntity(buildPath(uri, params.keySet()),
|
|
||||||
null,
|
|
||||||
responseType,
|
|
||||||
params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void put(URI uri, Consumer<Map<String, String>> paramsConsumer) {
|
|
||||||
var params = new HashMap<String, String>();
|
|
||||||
paramsConsumer.accept(params);
|
|
||||||
|
|
||||||
var template = restTemplate.get();
|
|
||||||
template.put(buildPath(uri, params.keySet()), params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(URI uri, Consumer<Map<String, String>> paramsConsumer) {
|
|
||||||
var params = new HashMap<String, String>();
|
|
||||||
paramsConsumer.accept(params);
|
|
||||||
|
|
||||||
var template = restTemplate.get();
|
|
||||||
template.delete(buildPath(uri, params.keySet()), params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildPath(URI uri, Collection<String> paramKeys) {
|
|
||||||
var path = new StringBuilder(serverUrl.resolve(uri) + "?");
|
var path = new StringBuilder(serverUrl.resolve(uri) + "?");
|
||||||
int left = paramKeys.size();
|
int left = paramKeys.size();
|
||||||
for (var key: paramKeys) {
|
for (var key: paramKeys) {
|
||||||
@ -92,4 +66,9 @@ public class RestUtil {
|
|||||||
}
|
}
|
||||||
return path.toString();
|
return path.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ParamsConsumer extends Consumer<Map<String, String>> {
|
||||||
|
|
||||||
|
ParamsConsumer NONE = map -> {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,18 +3,18 @@ package ru.dragonestia.picker.cp.repository.impl;
|
|||||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.web.client.HttpClientErrorException;
|
import org.springframework.http.HttpMethod;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.cp.model.dto.RoomDTO;
|
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
|
||||||
import ru.dragonestia.picker.cp.repository.RoomRepository;
|
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomInfoResponse;
|
import ru.dragonestia.picker.api.model.Node;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomListResponse;
|
import ru.dragonestia.picker.api.model.Room;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomRegisterResponse;
|
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.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@ -25,76 +25,44 @@ public class RoomRepositoryImpl implements RoomRepository {
|
|||||||
private final RestUtil rest;
|
private final RestUtil rest;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RoomDTO> all(Node node) {
|
public void register(Room room) throws NodeNotFoundException, InvalidRoomIdentifierException, RoomAlreadyExistException {
|
||||||
var entity = rest.getEntity(URI.create("/nodes/" + node.id() + "/rooms"),
|
rest.query("/nodes/" + room.getNodeId() + "/rooms", HttpMethod.POST, params -> {
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void register(Room room) {
|
|
||||||
try {
|
|
||||||
var response = rest.post(URI.create("/nodes/" + room.getNodeId() + "/rooms"),
|
|
||||||
RoomRegisterResponse.class,
|
|
||||||
params -> {
|
|
||||||
params.put("roomId", room.getId());
|
params.put("roomId", room.getId());
|
||||||
params.put("slots", Integer.toString(room.getSlots().slots()));
|
params.put("slots", Integer.toString(room.getSlots()));
|
||||||
params.put("payload", room.getPayload());
|
params.put("payload", room.getPayload());
|
||||||
params.put("locked", Boolean.toString(room.isLocked()));
|
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
|
@Override
|
||||||
public void remove(Room room) {
|
public void remove(Room room) throws NodeNotFoundException {
|
||||||
rest.delete(URI.create("/nodes/" + room.getNodeId() + "/rooms/" + room.getId()), params -> {});
|
rest.query("/nodes/" + room.getNodeId() + "/rooms/" + room.getId(), HttpMethod.DELETE, params -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(Node node, RoomDTO room) {
|
public void remove(Node node, Room.Short room) throws NodeNotFoundException {
|
||||||
rest.delete(URI.create("/nodes/" + node.id() + "/rooms/" + room.id()), params -> {});
|
rest.query("/nodes/" + node.getId() + "/rooms/" + room.id(), HttpMethod.DELETE, params -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Room> find(Node node, String roomId) {
|
public List<Room.Short> all(Node node) throws NodeNotFoundException {
|
||||||
|
return rest.query("/nodes/" + node.getId() + "/rooms", HttpMethod.GET, RoomListResponse.class, params -> {}).rooms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Room> find(Node node, String roomId) throws NodeNotFoundException {
|
||||||
try {
|
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());
|
return Optional.of(response.room());
|
||||||
} catch (Exception ex) {
|
} catch (RoomNotFoundException ex) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void lock(Room room, boolean value) {
|
public void lock(Room room, boolean value) throws NodeNotFoundException, RoomNotFoundException {
|
||||||
try {
|
rest.query("/nodes/%s/rooms/%s/lock".formatted(room.getNodeId(), room.getId()), HttpMethod.PUT, params -> {
|
||||||
rest.post(URI.create(room.createApiURI() + "/lock"), Boolean.class, params -> {
|
|
||||||
params.put("newState", Boolean.toString(value));
|
params.put("newState", Boolean.toString(value));
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
|
||||||
log.throwing(ex);
|
|
||||||
throw new Error("Error when changing room locked state");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,15 @@ package ru.dragonestia.picker.cp.repository.impl;
|
|||||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
import org.springframework.http.HttpMethod;
|
||||||
import ru.dragonestia.picker.cp.model.User;
|
import ru.dragonestia.picker.api.exception.NodeNotFoundException;
|
||||||
import ru.dragonestia.picker.cp.repository.UserRepository;
|
import ru.dragonestia.picker.api.exception.RoomAreFullException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.LinkUsersWithRoomResponse;
|
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
|
||||||
import ru.dragonestia.picker.cp.repository.impl.response.RoomUserListResponse;
|
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.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -21,47 +23,29 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
private final RestUtil rest;
|
private final RestUtil rest;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void linkWithRoom(Room room, Collection<User> users, boolean force) {
|
public void linkWithRoom(Room room, Collection<User> users, boolean force) throws NodeNotFoundException, RoomNotFoundException, RoomAreFullException {
|
||||||
try {
|
rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
|
||||||
var response = rest.post(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())),
|
HttpMethod.POST,
|
||||||
LinkUsersWithRoomResponse.class,
|
|
||||||
params -> {
|
params -> {
|
||||||
params.put("userIds", String.join(",", users.stream().map(User::id).toList()));
|
params.put("userIds", String.join(",", users.stream().map(User::getId).toList()));
|
||||||
params.put("force", Boolean.toString(force));
|
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unlinkFromRoom(Room room, Collection<User> users) {
|
public void unlinkFromRoom(Room room, Collection<User> users) throws NodeNotFoundException, RoomNotFoundException {
|
||||||
try {
|
rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
|
||||||
rest.delete(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())),
|
HttpMethod.DELETE,
|
||||||
params -> params.put("userIds", String.join(",", users.stream().map(User::id).toList())));
|
params -> {
|
||||||
} catch (Exception ex) {
|
params.put("userIds", String.join(",", users.stream().map(User::getId).toList()));
|
||||||
log.throwing(ex);
|
});
|
||||||
throw new Error("Internal error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<User> all(Room room) {
|
public List<User> all(Room room) throws NodeNotFoundException, RoomNotFoundException {
|
||||||
try {
|
return rest.query("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId()),
|
||||||
var response = rest.get(URI.create("/nodes/%s/rooms/%s/users".formatted(room.getNodeId(), room.getId())),
|
HttpMethod.GET,
|
||||||
RoomUserListResponse.class,
|
RoomUserListResponse.class,
|
||||||
params -> {});
|
params -> {}).users();
|
||||||
|
|
||||||
return response.users();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.throwing(ex);
|
|
||||||
throw new Error("Internal error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
|
||||||
|
|
||||||
public record LinkUsersWithRoomResponse(boolean success, String message, int usedSlots, int totalSlots) {}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
|
||||||
|
|
||||||
import ru.dragonestia.picker.cp.model.Node;
|
|
||||||
|
|
||||||
public record NodeDetailsResponse(Node node) {}
|
|
||||||
@ -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<Node> nodes) {}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
|
||||||
|
|
||||||
public record NodeRegisterResponse(boolean success, String message) {}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
package ru.dragonestia.picker.cp.repository.impl.response;
|
|
||||||
|
|
||||||
import ru.dragonestia.picker.cp.model.Room;
|
|
||||||
|
|
||||||
public record RoomInfoResponse(Room room) {}
|
|
||||||
@ -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<RoomDTO> rooms) {}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user