!refactored models, repositories, services and controllers

This commit is contained in:
Andrey Terentev 2024-05-11 02:14:06 +07:00 committed by Andrey Terentev
parent 8767654f12
commit 5c0b157414
63 changed files with 741 additions and 703 deletions

View File

@ -11,7 +11,7 @@ configurations {
}
dependencies {
implementation project(":client-api")
//implementation project(":client-api")
developmentOnly("org.springframework.boot:spring-boot-devtools")
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-security'

View File

@ -57,30 +57,30 @@ public class UserMetricsAspect {
@After(value = "execution(void ru.dragonestia.picker.repository.InstanceRepository.create(ru.dragonestia.picker.model.instance.Instance)) && args(instance)", argNames = "instance")
void onCreateNode(Instance instance) {
var nodeId = instance.getIdentifier();
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId).users())
.tag("nodeId", nodeId)
var nodeId = instance.getId();
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId.getValue()).users())
.tag("nodeId", nodeId.getValue())
.register(meterRegistry);
var counter = Counter.builder("roompicker_picks")
.tag("nodeId", nodeId)
.tag("nodeId", nodeId.getValue())
.baseUnit("1s")
.register(meterRegistry);
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId).locked())
.tag("nodeId", nodeId)
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId.getValue()).locked())
.tag("nodeId", nodeId.getValue())
.register(meterRegistry);
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance).size())
.tag("nodeId", nodeId)
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance.getId()).size())
.tag("nodeId", nodeId.getValue())
.register(meterRegistry);
data.put(nodeId, new NodeData(gauge, new AtomicInteger(0), counter, new AtomicInteger(0), lockedGauge, roomsGauge));
data.put(nodeId.getValue(), new NodeData(gauge, new AtomicInteger(0), counter, new AtomicInteger(0), lockedGauge, roomsGauge));
}
@After(value = "execution(* ru.dragonestia.picker.repository.InstanceRepository.delete(ru.dragonestia.picker.model.instance.Instance)) && args(instance)", argNames = "instance")
void onDeleteNode(Instance instance) {
var data = this.data.remove(instance.getIdentifier());
var data = this.data.remove(instance.getId().getValue());
meterRegistry.remove(data.usersGauge());
meterRegistry.remove(data.picksPerMinute());
@ -90,17 +90,17 @@ public class UserMetricsAspect {
@AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pick(ru.dragonestia.picker.model.instance.Instance, *)) && args(instance, ..)", argNames = "instance")
void onPickRoom(Instance instance) {
data.get(instance.getIdentifier()).picksPerMinute().increment();
data.get(instance.getId().getValue()).picksPerMinute().increment();
}
@Scheduled(fixedDelay = 3_000)
void updateUserMetrics() {
entityRepository.countEntitiesForNodes().forEach((nodeId, users) -> {
entityRepository.countEntitiesForInstances().forEach((nodeId, users) -> {
Optional.ofNullable(data.get(nodeId)).ifPresent(node -> node.users().set(users));
});
containerRepository.all().forEach(nodeContainer -> {
var locked = data.get(nodeContainer.getInstance().getIdentifier()).locked();
var locked = data.get(nodeContainer.getInstance().getId().getValue()).locked();
locked.set(0);
nodeContainer.allRooms().forEach(roomContainer -> {

View File

@ -10,15 +10,14 @@ import org.springframework.context.annotation.Profile;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.interceptor.DebugInterceptor;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.factory.RoomFactory;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.model.room.RoomId;
import ru.dragonestia.picker.model.room.factory.RoomFactory;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.EntityRepository;
@ -46,9 +45,9 @@ public class TestConfig implements WebMvcConfigurer {
@Bean
void createInstances() {
createInstanceWithContent(new Instance(NodeIdentifier.of("game-servers"), PickingMethod.ROUND_ROBIN, false));
createInstanceWithContent(new Instance(NodeIdentifier.of("game-lobbies"), PickingMethod.LEAST_PICKED, false));
createInstanceWithContent(new Instance(NodeIdentifier.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false));
createInstanceWithContent(new Instance(InstanceId.of("game-servers"), PickingMethod.ROUND_ROBIN, false));
createInstanceWithContent(new Instance(InstanceId.of("game-lobbies"), PickingMethod.LEAST_PICKED, false));
createInstanceWithContent(new Instance(InstanceId.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false));
}
@SneakyThrows
@ -58,17 +57,17 @@ public class TestConfig implements WebMvcConfigurer {
for (int i = 1; i <= 5; i++) {
var slots = 5 * i;
var room = roomFactory.create(RoomIdentifier.of("test-" + i), instance, slots, json.writeValueAsString(generatePayload()), false);
var room = roomFactory.create(RoomId.of("test-" + i), instance, slots, json.writeValueAsString(generatePayload()), false);
roomRepository.create(room);
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) {
var user = new Entity(EntityIdentifier.of("test-user-" + rand.nextInt(20)));
var user = EntityId.of("test-user-" + rand.nextInt(20));
entityRepository.linkWithRoom(room, List.of(user), false);
}
}
for (int i = 0; i < 5; i++) {
var room = roomFactory.create(RoomIdentifier.of(randomUUID().toString()), instance, IRoom.UNLIMITED_SLOTS, json.writeValueAsString(generatePayload()), false);
var room = roomFactory.create(RoomId.random(), instance, -1, json.writeValueAsString(generatePayload()), false);
room.setLocked((i & 1) == 0);
roomRepository.create(room);
}

View File

@ -1,52 +1,59 @@
package ru.dragonestia.picker.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.model.account.ResponseAccount;
import ru.dragonestia.picker.api.repository.response.AllAccountsResponse;
import ru.dragonestia.picker.controller.response.ResponseObject;
import java.util.List;
@Log4j2
@RestController
@RequestMapping("/accounts")
@RequiredArgsConstructor
public class AccountsController {
@GetMapping("/current")
ResponseAccount currentAccount() {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
List<String> listAccounts() {
throw new UnsupportedOperationException("Not implemented");
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/{accountId}")
ResponseEntity<ResponseAccount> findAccount(@PathVariable String accountId) {
@GetMapping("/target/{accountId}")
ResponseObject.Account targetAccountDetails(@PathVariable String accountId) {
throw new UnsupportedOperationException("Not implemented");
}
@GetMapping
AllAccountsResponse allAccounts() {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/list")
ResponseObject.Account listAccountsDetails(@RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
ResponseAccount registerAccount(@RequestParam String username, @RequestParam String password, @RequestParam(defaultValue = "") String permissions) {
ResponseEntity<Void> createAccount(@RequestParam String username,
@RequestParam String password,
@RequestParam List<String> permissions) {
throw new UnsupportedOperationException("Not implemented");
}
@PutMapping("/{accountId}")
ResponseEntity<?> updatePermissions(@PathVariable String accountId, @RequestParam(defaultValue = "") String permissions) {
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/target/{accountId}")
ResponseEntity<Void> removeAccount(@PathVariable String accountId) {
throw new UnsupportedOperationException("Not implemented");
}
@DeleteMapping("/{accountId}")
ResponseEntity<?> removeAccount(@PathVariable String accountId) {
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/target/{accountId}/permissions")
ResponseEntity<Void> setPermissions(@PathVariable String accountId,
@RequestParam List<String> permissions) {
throw new UnsupportedOperationException("Not implemented");
}
@PreAuthorize("hasRole('ADMIN') || principal.username.equals(accountId)")
@PutMapping("/{accountId}/password")
@PutMapping("/target/{accountId}/password")
ResponseEntity<?> changePassword(@PathVariable String accountId, @RequestParam String newPassword) {
throw new UnsupportedOperationException("Not implemented");
}

View File

@ -1,41 +1,28 @@
package ru.dragonestia.picker.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.repository.response.LinkedRoomsWithUserResponse;
import ru.dragonestia.picker.api.repository.response.SearchUserResponse;
import ru.dragonestia.picker.api.repository.response.UserDetailsResponse;
@Tag(name = "Users", description = "Entity management")
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
@RestController
@RequestMapping("/users")
@RequestMapping("/entities")
public class EntityController {
@Operation(summary = "Search user by identifier")
@GetMapping("/search")
SearchUserResponse search(
@Parameter(description = "Entity identifier input") @RequestParam(name = "input") String input
) {
List<String> search(@RequestParam String input) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Get user info")
@GetMapping("/{userId}")
UserDetailsResponse find(
@Parameter(description = "Entity identifier") @PathVariable(value = "userId") String userId
) {
@GetMapping("/target/rooms")
List<String> find(@RequestParam String id) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Get rooms linked with user")
@GetMapping("/{userId}/rooms")
LinkedRoomsWithUserResponse roomsOf(
@Parameter(description = "Entity identifier") @PathVariable(value = "userId") String userId
) {
@GetMapping("/list/rooms")
Map<String, List<String>> roomsOf(@RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
}

View File

@ -1,47 +1,33 @@
package ru.dragonestia.picker.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.repository.response.LinkUsersWithRoomResponse;
import ru.dragonestia.picker.api.repository.response.RoomUserListResponse;
@Tag(name = "Users", description = "Entity management")
import java.util.List;
@RequiredArgsConstructor
@RestController
@RequestMapping("/instances/{instanceId}/rooms/{roomId}/users")
public class EntityRoomController {
@Operation(summary = "Get users inside room")
@GetMapping
ResponseEntity<RoomUserListResponse> usersInsideRoom(
@Parameter(description = "Instance identifier") @PathVariable(name = "instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable(name = "roomId") String roomId
) {
List<String> entitiesInsideRoom(@PathVariable String instanceId, @PathVariable String roomId) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Link users with room")
@PostMapping
ResponseEntity<LinkUsersWithRoomResponse> linkUserWithRoom(
@Parameter(description = "Instance identifier") @PathVariable(name = "instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable(name = "roomId") String roomId,
@Parameter(description = "Entity identifiers", example = "user1,user2,user3") @RequestParam(name = "userIds") String userIds,
@Parameter(description = "Ignore slot limitation") @RequestParam(name = "force") boolean force
) {
ResponseEntity<Void> linkEntitiesWithRoom(@PathVariable String instanceId,
@PathVariable String roomId,
@RequestParam List<String> entities,
@RequestParam(defaultValue = "false") boolean force) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Unlink users from room")
@DeleteMapping
ResponseEntity<?> unlinkUsersForBucket(
@Parameter(description = "Instance identifier") @PathVariable(name = "instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable(name = "roomId") String roomId,
@Parameter(description = "Entity identifiers", example = "user1,user2,user3") @RequestParam(name = "userIds") String userIds
) {
ResponseEntity<Void> unlinkEntitiesForBucket(@PathVariable String instanceId,
@PathVariable String roomId,
@RequestParam List<String> entities) {
throw new UnsupportedOperationException("Not implemented");
}
}

View File

@ -1,85 +1,9 @@
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(InstanceNotFoundException.class)
ResponseEntity<?> nodeNotFound(InstanceNotFoundException 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(InvalidInstanceIdentifierException.class)
ResponseEntity<?> invalidNodeIdentifier(InvalidInstanceIdentifierException ex) {
return create(400, ex);
}
@ExceptionHandler(InvalidRoomIdentifierException.class)
ResponseEntity<?> invalidRoomIdentifier(InvalidRoomIdentifierException ex) {
return create(400, ex);
}
@ExceptionHandler(InstanceAlreadyExistException.class)
ResponseEntity<?> nodeAlreadyExists(InstanceAlreadyExistException 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);
}
@ExceptionHandler(NotPersistedNodeException.class)
ResponseEntity<?> notPersistedNode(NotPersistedNodeException ex) {
return create(400, ex);
}
@ExceptionHandler(AccountDoesNotExistsException.class)
ResponseEntity<?> accountDoesNotExists(AccountDoesNotExistsException ex) {
return create(404, ex);
}
@ExceptionHandler({PermissionNotFoundException.class})
ResponseEntity<?> permissionNotFound(PermissionNotFoundException ex) {
return create(400, ex);
}
@ExceptionHandler({ConstantAdminParamsException.class})
ResponseEntity<?> constantAdminParams(ConstantAdminParamsException ex) {
return create(401, 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));
}
// TODO
}

View File

@ -1,60 +1,52 @@
package ru.dragonestia.picker.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.repository.response.NodeDetailsResponse;
import ru.dragonestia.picker.api.repository.response.NodeListResponse;
import ru.dragonestia.picker.api.repository.response.PickedRoomResponse;
import ru.dragonestia.picker.controller.response.ResponseObject;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import java.util.List;
@Tag(name = "Nodes", description = "Instance management")
@RestController
@RequestMapping("/instances")
@RequiredArgsConstructor
public class InstanceController {
@Operation(summary = "Get all nodes")
@GetMapping
NodeListResponse allInstances() {
List<String> listInstances() {
throw new UnsupportedOperationException("Not implemented");
}
@GetMapping("/target/{instanceId}")
ResponseObject.Instance targetInstanceDetails(@PathVariable String instanceId) {
throw new UnsupportedOperationException("Not implemented");
}
@GetMapping("/list")
List<ResponseObject.Instance> listInstancesDetails(@RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Register new node")
@PostMapping
ResponseEntity<?> registerInstance(
@Parameter(description = "Instance identifier") @RequestParam(name = "instanceId") String instanceId,
@Parameter(description = "Picking method method") @RequestParam(name = "method") PickingMethod method,
@Parameter(description = "Save node") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) {
ResponseEntity<Void> createInstance(@RequestParam String instanceId,
@RequestParam PickingMethod method,
@RequestParam(defaultValue = "false") boolean persist) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Get node details")
@GetMapping("/{instanceId}")
ResponseEntity<NodeDetailsResponse> instanceDetails(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId
) {
@DeleteMapping("/target/{instanceId}")
ResponseEntity<Void> deleteInstance(@PathVariable String instanceId) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Unregister node")
@DeleteMapping("/{instanceId}")
ResponseEntity<?> removeInstance(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId
) {
@DeleteMapping("/list")
ResponseEntity<Void> deleteInstances(@RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Pick node for users")
@PostMapping("/{instanceId}/pick")
ResponseEntity<PickedRoomResponse> pickRoom(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId,
@RequestBody String userIds
) {
@PostMapping("/target/{instanceId}/pick")
ResponseObject.PickedRoom pickRoom(@PathVariable String instanceId, @RequestBody List<String> entities) {
throw new UnsupportedOperationException("Not implemented");
}
}

View File

@ -1,68 +1,54 @@
package ru.dragonestia.picker.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.dragonestia.picker.api.repository.response.RoomInfoResponse;
import ru.dragonestia.picker.api.repository.response.RoomListResponse;
import ru.dragonestia.picker.controller.response.ResponseObject;
import java.util.List;
@Tag(name = "Rooms", description = "Room management")
@RestController
@RequestMapping("/instances/{instanceId}/rooms")
@RequestMapping("/instances/target/{instanceId}/rooms")
@RequiredArgsConstructor
public class RoomController {
@Operation(summary = "Get all rooms from node")
@GetMapping
ResponseEntity<RoomListResponse> all(
@Parameter(description = "Instance identifier") @PathVariable(name = "instanceId") String instanceId
) {
List<String> listRooms(@PathVariable String instanceId) {
throw new UnsupportedOperationException("Not implemented");
}
@GetMapping("/target/{roomId}")
ResponseObject.Room targetRoomDetails(@PathVariable String instanceId, @PathVariable String roomId) {
throw new UnsupportedOperationException("Not implemented");
}
@GetMapping("/list")
List<ResponseObject.Room> listRoomDetails(@PathVariable String instanceId, @RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Register new room")
@PostMapping
ResponseEntity<?> register(
@Parameter(description = "Instance identifier") @PathVariable(name = "instanceId") String instanceId,
@Parameter(description = "Room identifier") @RequestParam(name = "roomId") String roomId,
@Parameter(description = "Maximum users count in room") @RequestParam(name = "slots") int slots,
@Parameter(description = "Payload. Some data") @RequestParam(name = "payload") String payload,
@Parameter(description = "Lock for picking") @RequestParam(name = "locked", required = false, defaultValue = "false") boolean locked,
@Parameter(description = "Save room") @RequestParam(name = "persist", required = false, defaultValue = "false") boolean persist
) {
ResponseEntity<Void> createRoom(@PathVariable String instanceId,
@RequestParam String id,
@RequestParam int slots,
@RequestParam String payload,
@RequestParam(defaultValue = "false") boolean locked,
@RequestParam(defaultValue = "false") boolean persist) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Unregister room")
@DeleteMapping("/{roomId}")
ResponseEntity<?> remove(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable("roomId") String roomId
) {
@DeleteMapping("/target/{roomId}")
ResponseEntity<Void> deleteRoom(@PathVariable String instanceId, @PathVariable String roomId) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Get room details")
@GetMapping("/{roomId}")
ResponseEntity<RoomInfoResponse> info(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable("roomId") String roomId
) {
@DeleteMapping("/list")
ResponseEntity<Void> deleteRooms(@PathVariable String instanceId, @RequestParam List<String> id) {
throw new UnsupportedOperationException("Not implemented");
}
@Operation(summary = "Lock/unlock room")
@ApiResponse(description = "New lock state")
@PutMapping("/{roomId}/lock")
ResponseEntity<Boolean> lockRoom(
@Parameter(description = "Instance identifier") @PathVariable("instanceId") String instanceId,
@Parameter(description = "Room identifier") @PathVariable("roomId") String roomId,
@Parameter(description = "New state for Lock property") @RequestParam(name = "newState") boolean value
) {
@PutMapping("/target/{roomId}/lock")
ResponseEntity<Void> lockRoom(@PathVariable String instanceId, @PathVariable String roomId, @RequestParam boolean newState) {
throw new UnsupportedOperationException("Not implemented");
}
}

View File

@ -1,16 +1,12 @@
package ru.dragonestia.picker.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.dragonestia.picker.api.repository.response.RoomPickerInfoResponse;
import ru.dragonestia.picker.controller.response.RoomPickerInfoResponse;
@Tag(name = "RoomPicker")
@RestController
public class RoomPickerController {
@Operation(summary = "Server info")
@GetMapping("/info")
RoomPickerInfoResponse info() {
return new RoomPickerInfoResponse("v0.0.1");

View File

@ -4,12 +4,14 @@ import jakarta.validation.constraints.NotNull;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.controller.graphql.object.ObjectInstance;
import ru.dragonestia.picker.controller.graphql.object.ObjectRoom;
import ru.dragonestia.picker.controller.graphql.object.ObjectEntity;
import ru.dragonestia.picker.controller.graphql.object.type.DataProvider;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.RoomId;
import ru.dragonestia.picker.service.InstanceService;
import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.service.EntityService;
@ -40,40 +42,40 @@ public class GraphqlController {
@QueryMapping
ObjectInstance instanceById(@Argument String id) {
return instanceService.find(id)
return instanceService.find(InstanceId.of(id))
.map(node -> new ObjectInstance(node, dataProvider))
.orElse(null);
}
@QueryMapping
List<ObjectRoom> allRooms(@NotNull String nodeId) {
var node = instanceService.find(nodeId).orElse(null);
var node = instanceService.find(InstanceId.of(nodeId)).orElse(null);
if (node == null) return null;
return roomService.all(node).stream()
return roomService.all(node.getId()).stream()
.map(room -> new ObjectRoom(room, dataProvider))
.toList();
}
@QueryMapping
ObjectRoom roomById(@Argument String nodeId, @NotNull String roomId) {
var node = instanceService.find(nodeId).orElse(null);
var node = instanceService.find(InstanceId.of(nodeId)).orElse(null);
if (node == null) return null;
return roomService.find(node, roomId)
return roomService.find(node.getId(), RoomId.of(roomId))
.map(room -> new ObjectRoom(room, dataProvider))
.orElse(null);
}
@QueryMapping
ObjectEntity entityById(@Argument String id) {
return new ObjectEntity(new Entity(EntityIdentifier.of(id)), dataProvider);
return new ObjectEntity(new Entity(EntityId.of(id)), dataProvider);
}
@QueryMapping
List<ObjectEntity> searchEntity(@Argument String input) {
return entityService.searchEntities(input).stream()
.map(user -> new ObjectEntity(new Entity(user.getIdentifierObject()), dataProvider))
return entityService.searchEntities(EntityId.of(input)).stream()
.map(user -> new ObjectEntity(new Entity(user.getId()), dataProvider))
.toList();
}
}

View File

@ -15,7 +15,7 @@ public class ObjectEntity {
private List<ObjectRoom> cachedRooms = null;
public @NotNull String getId() {
return entity.getIdentifier();
return entity.getId().getValue();
}
public List<ObjectRoom> getRooms() {
@ -23,7 +23,7 @@ public class ObjectEntity {
return cachedRooms;
}
cachedRooms = dataProvider.entityService().getEntityRooms(entity).stream()
cachedRooms = dataProvider.entityService().getEntityRooms(entity.getId()).stream()
.map(room -> new ObjectRoom(room, dataProvider))
.toList();

View File

@ -14,7 +14,7 @@ public class ObjectInstance {
private List<ObjectRoom> cachedRooms = null;
public String getId() {
return instance.getIdentifier();
return instance.getId().getValue();
}
public String getMethod() {
@ -26,7 +26,7 @@ public class ObjectInstance {
return cachedRooms;
}
cachedRooms = dataProvider.roomService().all(instance).stream()
cachedRooms = dataProvider.roomService().all(instance.getId()).stream()
.map(room -> new ObjectRoom(room, dataProvider))
.toList();

View File

@ -14,21 +14,21 @@ public class ObjectRoom {
private List<ObjectEntity> cachedUsers = null;
public String getId() {
return room.getIdentifier();
return room.getId().getValue();
}
public String getInstanceId() {
return room.getInstanceIdentifier();
return room.getInstance().getId().getValue();
}
public ObjectInstance getInstance() {
return dataProvider.instanceService().find(room.getInstanceIdentifier())
return dataProvider.instanceService().find(room.getInstance().getId())
.map(node -> new ObjectInstance(node, dataProvider))
.orElseThrow();
}
public int getSlots() {
return room.getMaxSlots();
return room.getSlots();
}
public String getPayload() {

View File

@ -0,0 +1,19 @@
package ru.dragonestia.picker.controller.response;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import java.util.List;
public final class ResponseObject {
private ResponseObject() {}
public record Instance(String id, PickingMethod method, boolean persist) {}
public record Room(String id, String nodeId, int slots, boolean locked, boolean persist, String payload) {}
public record PickedRoom(Room room, List<EntityId> entities) {}
public record Account(String id, List<String> permissions, boolean locked) {}
}

View File

@ -0,0 +1,3 @@
package ru.dragonestia.picker.controller.response;
public record RoomPickerInfoResponse(String version) {}

View File

@ -0,0 +1,8 @@
package ru.dragonestia.picker.exception;
public class AdminAccountMutationException extends RuntimeException {
public AdminAccountMutationException() {
super("Cannot mutate admin account");
}
}

View File

@ -0,0 +1,19 @@
package ru.dragonestia.picker.exception;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.RoomId;
public class AlreadyExistsException extends RuntimeException {
public AlreadyExistsException(String message) {
super(message);
}
public static AlreadyExistsException forInstance(InstanceId instanceId) {
return new AlreadyExistsException("Instance with id '%s' already created".formatted(instanceId.getValue()));
}
public static AlreadyExistsException forRoom(InstanceId instanceId, RoomId roomId) {
return new AlreadyExistsException("Room with id '%s' already exists in instance '%s".formatted(roomId.getValue(), instanceId.getValue()));
}
}

View File

@ -0,0 +1,19 @@
package ru.dragonestia.picker.exception;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.RoomId;
public class ConflictingPersistParametersException extends RuntimeException {
public ConflictingPersistParametersException(String message) {
super(message);
}
public static ConflictingPersistParametersException forRoom(InstanceId instanceId, RoomId roomId) {
return new ConflictingPersistParametersException("Tried create persisted room '%s' for not persisted instance '%s'"
.formatted(
roomId.getValue(),
instanceId.getValue()
));
}
}

View File

@ -0,0 +1,19 @@
package ru.dragonestia.picker.exception;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.RoomId;
public class DoesNotExistsException extends RuntimeException {
public DoesNotExistsException(String message) {
super(message);
}
public static DoesNotExistsException forInstance(InstanceId id) {
return new DoesNotExistsException("Does not exists instance with id '%s'".formatted(id.toString()));
}
public static DoesNotExistsException forRoom(RoomId id) {
return new DoesNotExistsException("Does not exists room with id '%s'".formatted(id.toString()));
}
}

View File

@ -0,0 +1,14 @@
package ru.dragonestia.picker.exception;
import org.jetbrains.annotations.Nullable;
public class InvalidIdentifierException extends RuntimeException {
public InvalidIdentifierException(String message) {
super(message);
}
public static InvalidIdentifierException taken(@Nullable String input) {
return new InvalidIdentifierException("Taken identifier: " + input);
}
}

View File

@ -0,0 +1,10 @@
package ru.dragonestia.picker.exception;
import ru.dragonestia.picker.model.instance.InstanceId;
public class NoRoomsAvailableException extends RuntimeException {
public NoRoomsAvailableException(InstanceId instanceId) {
super("There are no rooms available in instance '" + instanceId.getValue() + "'");
}
}

View File

@ -0,0 +1,11 @@
package ru.dragonestia.picker.exception;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.RoomId;
public class RoomAreFullException extends RuntimeException {
public RoomAreFullException(InstanceId instanceId, RoomId roomId) {
super("Room '%s' in instance '%s' are full".formatted(roomId.getValue(), instanceId.toString()));
}
}

View File

@ -1,66 +1,37 @@
package ru.dragonestia.picker.model.account;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.userdetails.UserDetails;
import ru.dragonestia.picker.api.model.account.IAccount;
import ru.dragonestia.picker.api.model.account.ResponseAccount;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
public class Account implements IAccount, UserDetails {
public class Account implements UserDetails {
private final String username;
@Getter private final AccountId id;
@Getter private final String username;
private final String lowerUsername;
private String password;
@Getter @Setter private String password;
private Set<Permission> permissions = new HashSet<>();
private boolean locked = false;
private boolean enabled = true;
@Getter @Setter private boolean locked = false;
@Getter @Setter private boolean enabled = true;
public Account(@NotNull String username, @NotNull String password) {
this.username = username;
public Account(AccountId id, String password) {
this.id = id;
this.username = id.getValue();
this.lowerUsername = username.toLowerCase();
this.password = password;
}
@Override
public @NotNull Collection<Permission> getAuthorities() {
public Collection<Permission> getAuthorities() {
return permissions;
}
@Override
public boolean isLocked() {
return locked;
}
@Contract("_ -> this")
public @NotNull Account setAuthorities(@NotNull Set<Permission> permissions) {
public void setAuthorities(Set<Permission> permissions) {
this.permissions = permissions;
return this;
}
@Override
public @NotNull String getPassword() {
return password;
}
@Override
public @NotNull Set<String> getPermissions() {
return getAuthorities().stream().map(Enum::name).collect(Collectors.toSet());
}
@Contract("_ -> this")
public @NotNull Account setPassword(String value) {
password = value;
return this;
}
@Override
public @NotNull String getUsername() {
return username;
}
@Override
@ -73,28 +44,11 @@ public class Account implements IAccount, UserDetails {
return !locked;
}
@Contract("_ -> this")
public @NotNull Account setLocked(boolean value) {
locked = value;
return this;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Contract("_ -> this")
public @NotNull Account setEnabled(boolean value) {
enabled = value;
return this;
}
@Override
public int hashCode() {
return lowerUsername.hashCode();
@ -109,8 +63,4 @@ public class Account implements IAccount, UserDetails {
}
return false;
}
public @NotNull ResponseAccount toResponseObject() {
return new ResponseAccount(username, password, getPermissions(), locked);
}
}

View File

@ -0,0 +1,42 @@
package ru.dragonestia.picker.model.account;
import lombok.Getter;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import java.util.Objects;
@Getter
public final class AccountId {
private final String value;
private AccountId(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AccountId accountId = (AccountId) o;
return Objects.equals(value, accountId.value);
}
public static AccountId of(String identifier) throws InvalidIdentifierException {
if (identifier.matches("^[aA-zZ\\d]{3,32}$")) {
return new AccountId(identifier);
}
throw InvalidIdentifierException.taken(identifier);
}
}

View File

@ -1,42 +1,24 @@
package ru.dragonestia.picker.model.entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.picker.api.model.user.IUser;
import ru.dragonestia.picker.api.model.user.ResponseUser;
import ru.dragonestia.picker.api.model.user.UserDetails;
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
import lombok.Getter;
public class Entity implements IUser {
@Getter
public class Entity {
private final String identifier;
private final EntityId id;
public Entity(@NotNull EntityIdentifier identifier) {
this.identifier = identifier.getValue();
}
@Override
public @NotNull String getIdentifier() {
return identifier;
}
@Override
public @Nullable String getDetail(@NotNull UserDetails detail) {
throw new UnsupportedOperationException();
}
public @NotNull ResponseUser toResponseObject() {
return new ResponseUser(identifier);
public Entity(EntityId id) {
this.id = id;
}
@Override
public String toString() {
return identifier;
return id.getValue();
}
@Override
public int hashCode() {
return identifier.hashCode();
return id.hashCode();
}
@Override
@ -44,7 +26,7 @@ public class Entity implements IUser {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Entity other) {
return identifier.equals(other.identifier);
return id.equals(other.id);
}
return false;
}

View File

@ -0,0 +1,42 @@
package ru.dragonestia.picker.model.entity;
import lombok.Getter;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import java.util.Objects;
@Getter
public final class EntityId {
private final String value;
private EntityId(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EntityId entityId = (EntityId) o;
return Objects.equals(value, entityId.value);
}
public static EntityId of(String identifier) throws InvalidIdentifierException {
if (identifier.matches("^[aA-zZ\\d-.\\s:@_;]{1,64}$")) {
return new EntityId(identifier);
}
throw InvalidIdentifierException.taken(identifier);
}
}

View File

@ -1,19 +0,0 @@
package ru.dragonestia.picker.model.factory;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.room.Room;
@Component
@RequiredArgsConstructor
public class RoomFactory {
@Contract("_, _, _, _, _ -> new")
public @NotNull Room create(@NotNull RoomIdentifier identifier, @NotNull Instance instance, int slots, @NotNull String payload, boolean persist) {
return new Room(identifier, instance, slots, payload, persist);
}
}

View File

@ -1,52 +1,24 @@
package ru.dragonestia.picker.model.instance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.picker.api.model.node.INode;
import ru.dragonestia.picker.api.model.node.NodeDetails;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.api.model.node.ResponseNode;
import ru.dragonestia.picker.api.repository.type.NodeIdentifier;
import lombok.Getter;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
public class Instance implements INode {
@Getter
public class Instance {
private final String identifier;
private final InstanceId id;
private final PickingMethod pickingMethod;
private final boolean persist;
public Instance(@NotNull NodeIdentifier identifier, @NotNull PickingMethod pickingMethod, boolean persist) {
this.identifier = identifier.getValue();
public Instance(InstanceId id, PickingMethod pickingMethod, boolean persist) {
this.id = id;
this.pickingMethod = pickingMethod;
this.persist = persist;
}
@Override
public @NotNull String getIdentifier() {
return identifier;
}
@Override
public @NotNull PickingMethod getPickingMethod() {
return pickingMethod;
}
@Override
public @NotNull Boolean isPersist() {
return persist;
}
@Override
public @Nullable String getDetail(@NotNull NodeDetails detail) {
throw new UnsupportedOperationException();
}
public @NotNull ResponseNode toResponseObject() {
return new ResponseNode(identifier, pickingMethod);
}
@Override
public int hashCode() {
return identifier.hashCode();
return id.hashCode();
}
@Override
@ -54,13 +26,13 @@ public class Instance implements INode {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Instance other) {
return identifier.equals(other.identifier);
return id.equals(other.id);
}
return false;
}
@Override
public String toString() {
return "{Instance id='%s'}".formatted(identifier);
return "{Instance id='%s'}".formatted(id);
}
}

View File

@ -0,0 +1,53 @@
package ru.dragonestia.picker.model.instance;
import lombok.Getter;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import java.util.Objects;
import java.util.Random;
@Getter
public final class InstanceId {
private static final Random random = new Random();
private final String value;
private InstanceId(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InstanceId that = (InstanceId) o;
return Objects.equals(value, that.value);
}
public static InstanceId of(String identifier) throws InvalidIdentifierException {
if (identifier.matches("^(?!-)[a-z\\d-]{0,31}[a-z\\d](?!-)$")) {
return new InstanceId(identifier);
}
throw InvalidIdentifierException.taken(identifier);
}
public static InstanceId random() {
char[] chars = new char[32];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) ('a' + random.nextInt('z' - 'a'));
}
return new InstanceId(new String(chars));
}
}

View File

@ -0,0 +1,7 @@
package ru.dragonestia.picker.model.instance.type;
public enum PickingMethod {
SEQUENTIAL_FILLING,
ROUND_ROBIN,
LEAST_PICKED
}

View File

@ -1,89 +1,32 @@
package ru.dragonestia.picker.model.room;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.picker.api.model.room.IRoom;
import ru.dragonestia.picker.api.model.room.ResponseRoom;
import ru.dragonestia.picker.api.model.room.RoomDetails;
import ru.dragonestia.picker.api.model.room.ShortResponseRoom;
import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import lombok.Getter;
import lombok.Setter;
import ru.dragonestia.picker.model.instance.Instance;
import java.util.Objects;
public class Room implements IRoom {
@Getter
public class Room {
private final String identifier;
private final String instanceIdentifier;
private final RoomId id;
private final Instance instance;
private final int slots;
private final String payload;
private final boolean persist;
private boolean locked = false;
@Setter private boolean locked = false;
public Room(@NotNull RoomIdentifier identifier, @NotNull Instance instance, int slots, @NotNull String payload, boolean persist) {
this.identifier = identifier.getValue();
this.instanceIdentifier = instance.getIdentifier();
public Room(RoomId id, Instance instance, int slots, String payload, boolean persist) {
this.id = id;
this.instance = instance;
this.slots = slots;
this.payload = payload;
this.persist = persist;
}
@Override
public @NotNull String getIdentifier() {
return identifier;
}
@Override
public @NotNull String getInstanceIdentifier() {
return instanceIdentifier;
}
@Override
public int getMaxSlots() {
return slots;
}
@Override
public boolean isLocked() {
return locked;
}
public void setLocked(boolean value) {
locked = value;
}
@Override
public @NotNull Boolean isPersist() {
return persist;
}
@Override
public @NotNull String getPayload() {
return payload;
}
@Override
public @Nullable String getDetail(@NotNull RoomDetails detail) {
throw new UnsupportedOperationException();
}
public boolean isAvailable(int usedSlots, int requiredSlots) {
if (locked) return false;
if (hasUnlimitedSlots()) return true;
return slots >= usedSlots + requiredSlots;
}
public @NotNull ResponseRoom toResponseObject() {
return new ResponseRoom(identifier, instanceIdentifier, slots, locked, payload);
}
public @NotNull ShortResponseRoom toShortResponseObject() {
return new ShortResponseRoom(identifier, instanceIdentifier, slots, locked);
}
@Override
public int hashCode() {
return Objects.hash(identifier, instanceIdentifier);
return Objects.hash(id, instance.getId());
}
@Override
@ -91,7 +34,7 @@ public class Room implements IRoom {
if (object == this) return true;
if (object == null) return false;
if (object instanceof Room other) {
return identifier.equals(other.identifier) && instanceIdentifier.equals(other.instanceIdentifier);
return id.equals(other.id) && instance.getId().equals(other.instance.getId());
}
return false;
}

View File

@ -0,0 +1,54 @@
package ru.dragonestia.picker.model.room;
import lombok.Getter;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.model.instance.InstanceId;
import java.util.Objects;
import java.util.Random;
@Getter
public final class RoomId {
private final static Random random = new Random();
private final String value;
private RoomId(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoomId roomId = (RoomId) o;
return Objects.equals(value, roomId.value);
}
public static RoomId of(String identifier) throws InvalidIdentifierException {
if (identifier.matches("^(?!-)[a-z\\d-]{0,31}[a-z\\d](?!-)$")) {
return new RoomId(identifier);
}
throw InvalidIdentifierException.taken(identifier);
}
public static RoomId random() {
char[] chars = new char[32];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) ('a' + random.nextInt('z' - 'a'));
}
return new RoomId(new String(chars));
}
}

View File

@ -0,0 +1,16 @@
package ru.dragonestia.picker.model.room.factory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.room.RoomId;
@Component
@RequiredArgsConstructor
public class RoomFactory {
public Room create(RoomId identifier, Instance instance, int slots, String payload, boolean persist) {
return new Room(identifier, instance, slots, payload, persist);
}
}

View File

@ -1,6 +1,7 @@
package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.exception.RoomAreFullException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
@ -9,17 +10,17 @@ import java.util.Map;
public interface EntityRepository {
void linkWithRoom(Room room, Collection<Entity> entities, boolean force) throws RoomAreFullException;
void linkWithRoom(Room room, Collection<EntityId> entities, boolean force) throws RoomAreFullException;
void unlinkWithRoom(Room room, Collection<Entity> entities);
void unlinkWithRoom(Room room, Collection<EntityId> entities);
Collection<Room> findAllLinkedEntityRooms(Entity entity);
Collection<Room> findAllLinkedEntityRooms(EntityId entity);
Collection<Entity> entitiesOf(Room room);
Collection<Entity> search(String input);
Collection<Entity> search(EntityId input);
int countAllEntities();
Map<String, Integer> countEntitiesForNodes();
Map<String, Integer> countEntitiesForInstances();
}

View File

@ -1,18 +1,19 @@
package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.InstanceAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import java.util.List;
import java.util.Optional;
public interface InstanceRepository {
void create(Instance instance) throws InstanceAlreadyExistException;
void create(Instance instance) throws AlreadyExistsException;
void delete(Instance instance);
void delete(InstanceId id);
Optional<Instance> findById(String nodeId);
Optional<Instance> findById(InstanceId id);
List<Instance> all();
}

View File

@ -1,10 +1,11 @@
package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.room.RoomId;
import java.util.Collection;
import java.util.Optional;
@ -12,13 +13,13 @@ import java.util.Set;
public interface RoomRepository {
void create(Room room) throws RoomAlreadyExistException;
void create(Room room) throws AlreadyExistsException;
void remove(Room room);
void remove(InstanceId instanceId, RoomId roomId);
Optional<Room> find(Instance instance, String identifier);
Optional<Room> find(InstanceId instanceId, RoomId roomId);
Collection<Room> all(Instance instance);
Collection<Room> all(InstanceId instanceId);
Room pick(Instance instance, Set<Entity> entities) throws NoRoomsAvailableException;
Room pick(InstanceId instanceId, Set<EntityId> entities) throws NoRoomsAvailableException;
}

View File

@ -2,8 +2,9 @@ package ru.dragonestia.picker.repository.impl;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.exception.InstanceAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
import ru.dragonestia.picker.repository.impl.type.EntityTransaction;
@ -15,25 +16,25 @@ import java.util.concurrent.ConcurrentHashMap;
@Component
public class ContainerRepository {
private final Map<String, InstanceContainer> containers = new ConcurrentHashMap<>();
private final Map<InstanceId, InstanceContainer> containers = new ConcurrentHashMap<>();
private EntityTransaction.Listener transactionListener = transaction -> {};
public void create(Instance instance) throws InstanceAlreadyExistException {
if (containers.containsKey(instance.getIdentifier())) {
throw new InstanceAlreadyExistException(instance.getIdentifier());
public void create(Instance instance) throws AlreadyExistsException {
if (containers.containsKey(instance.getId())) {
throw AlreadyExistsException.forInstance(instance.getId());
}
var container = new InstanceContainer(instance, transactionListener);
containers.put(instance.getIdentifier(), container);
containers.put(instance.getId(), container);
}
public void remove(@NotNull String instanceId) {
containers.remove(instanceId);
public void remove(InstanceId id) {
containers.remove(id);
}
public @NotNull Optional<InstanceContainer> findById(@NotNull String instanceId) {
return Optional.ofNullable(containers.get(instanceId));
public @NotNull Optional<InstanceContainer> findById(InstanceId id) {
return Optional.ofNullable(containers.get(id));
}
public @NotNull Collection<InstanceContainer> all() {

View File

@ -3,9 +3,9 @@ package ru.dragonestia.picker.repository.impl;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.exception.InstanceNotFoundException;
import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.api.exception.RoomNotFoundException;
import ru.dragonestia.picker.exception.DoesNotExistsException;
import ru.dragonestia.picker.exception.RoomAreFullException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.EntityRepository;
@ -20,14 +20,14 @@ public class EntityRepositoryImpl implements EntityRepository {
private final ContainerRepository containerRepository;
private final Map<Entity, Set<Room>> entityRooms = new ConcurrentHashMap<>();
private final Map<EntityId, Set<Room>> entityRooms = new ConcurrentHashMap<>();
@PostConstruct
void init() {
containerRepository.setTransactionListener(transaction -> {
synchronized (entityRooms) {
for (var entity: transaction.target()) {
var set = entityRooms.computeIfAbsent(entity, k -> new HashSet<>());
var set = entityRooms.computeIfAbsent(entity.getId(), k -> new HashSet<>());
set.add(transaction.room());
}
}
@ -35,9 +35,9 @@ public class EntityRepositoryImpl implements EntityRepository {
}
@Override
public void linkWithRoom(Room room, Collection<Entity> entities, boolean force) throws RoomAreFullException {
public void linkWithRoom(Room room, Collection<EntityId> entities, boolean force) throws RoomAreFullException {
synchronized (entityRooms) {
getRoomContainer(room).addEntities(entities, force);
getRoomContainer(room).addEntities(entities.stream().map(Entity::new).toList(), force);
for (var entity: entities) {
var set = entityRooms.computeIfAbsent(entity, k -> new HashSet<>());
@ -47,9 +47,9 @@ public class EntityRepositoryImpl implements EntityRepository {
}
@Override
public void unlinkWithRoom(Room room, Collection<Entity> entities) {
public void unlinkWithRoom(Room room, Collection<EntityId> entities) {
synchronized (entityRooms) {
getRoomContainer(room).removeEntities(entities);
getRoomContainer(room).removeEntities(entities.stream().map(Entity::new).toList());
for (var entity: entities) {
var set = entityRooms.get(entity);
@ -61,7 +61,7 @@ public class EntityRepositoryImpl implements EntityRepository {
}
@Override
public Collection<Room> findAllLinkedEntityRooms(Entity entity) {
public Collection<Room> findAllLinkedEntityRooms(EntityId entity) {
var result = entityRooms.get(entity);
return Collections.unmodifiableSet(result == null? new HashSet<>() : result);
}
@ -72,8 +72,12 @@ public class EntityRepositoryImpl implements EntityRepository {
}
@Override
public Collection<Entity> search(String input) {
return entityRooms.keySet().stream().filter(entity -> entity.getIdentifier().startsWith(input)).toList();
public Collection<Entity> search(EntityId input) {
var inputStr = input.getValue();
return entityRooms.keySet().stream()
.filter(entity -> entity.getValue().startsWith(inputStr))
.map(Entity::new)
.toList();
}
@Override
@ -82,14 +86,14 @@ public class EntityRepositoryImpl implements EntityRepository {
}
@Override
public Map<String, Integer> countEntitiesForNodes() {
public Map<String, Integer> countEntitiesForInstances() {
var result = new HashMap<String, Integer>();
containerRepository.all().forEach(nodeContainer -> {
var nodeId = nodeContainer.getInstance().getIdentifier();
var nodeId = nodeContainer.getInstance().getId();
nodeContainer.allRooms().forEach(roomContainer -> {
result.put(nodeId, result.getOrDefault(nodeId, 0) + roomContainer.countEntities());
result.put(nodeId.getValue(), result.getOrDefault(nodeId.getValue(), 0) + roomContainer.countEntities());
});
});
@ -97,9 +101,9 @@ public class EntityRepositoryImpl implements EntityRepository {
}
private RoomContainer getRoomContainer(Room room) {
return containerRepository.findById(room.getInstanceIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(room.getInstanceIdentifier()))
.findRoomById(room.getIdentifier())
.orElseThrow(() -> new RoomNotFoundException(room.getInstanceIdentifier(), room.getIdentifier()));
return containerRepository.findById(room.getInstance().getId())
.orElseThrow(() -> DoesNotExistsException.forInstance(room.getInstance().getId()))
.findRoomById(room.getId())
.orElseThrow(() -> DoesNotExistsException.forRoom(room.getId()));
}
}

View File

@ -2,8 +2,9 @@ package ru.dragonestia.picker.repository.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.exception.InstanceAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
@ -17,18 +18,18 @@ public class InstanceRepositoryImpl implements InstanceRepository {
private final ContainerRepository containerRepository;
@Override
public void create(Instance instance) throws InstanceAlreadyExistException {
public void create(Instance instance) throws AlreadyExistsException {
containerRepository.create(instance);
}
@Override
public void delete(Instance instance) {
containerRepository.remove(instance.getIdentifier());
public void delete(InstanceId instanceId) {
containerRepository.remove(instanceId);
}
@Override
public Optional<Instance> findById(String nodeId) {
return containerRepository.findById(nodeId).map(InstanceContainer::getInstance);
public Optional<Instance> findById(InstanceId id) {
return containerRepository.findById(id).map(InstanceContainer::getInstance);
}
@Override

View File

@ -2,12 +2,13 @@ package ru.dragonestia.picker.repository.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.exception.InstanceNotFoundException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.DoesNotExistsException;
import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.room.RoomId;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
@ -22,39 +23,39 @@ public class RoomRepositoryImpl implements RoomRepository {
private final ContainerRepository containerRepository;
@Override
public void create(Room room) throws RoomAlreadyExistException {
containerRepository.findById(room.getInstanceIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(room.getInstanceIdentifier()))
public void create(Room room) throws AlreadyExistsException {
containerRepository.findById(room.getInstance().getId())
.orElseThrow(() -> DoesNotExistsException.forInstance(room.getInstance().getId()))
.addRoom(room);
}
@Override
public void remove(Room room) {
containerRepository.findById(room.getInstanceIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(room.getInstanceIdentifier()))
.removeRoom(room);
public void remove(InstanceId instanceId, RoomId roomId) {
containerRepository.findById(instanceId)
.orElseThrow(() -> DoesNotExistsException.forInstance(instanceId))
.removeRoom(roomId);
}
@Override
public Optional<Room> find(Instance instance, String identifier) {
return containerRepository.findById(instance.getIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(instance.getIdentifier()))
.findRoomById(identifier)
public Optional<Room> find(InstanceId instanceId, RoomId roomId) {
return containerRepository.findById(instanceId)
.orElseThrow(() -> DoesNotExistsException.forInstance(instanceId))
.findRoomById(roomId)
.map(RoomContainer::getRoom);
}
@Override
public Collection<Room> all(Instance instance) {
return containerRepository.findById(instance.getIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(instance.getIdentifier()))
public Collection<Room> all(InstanceId instanceId) {
return containerRepository.findById(instanceId)
.orElseThrow(() -> DoesNotExistsException.forInstance(instanceId))
.allRooms()
.stream().map(RoomContainer::getRoom).toList();
}
@Override
public Room pick(Instance instance, Set<Entity> entities) throws NoRoomsAvailableException {
return containerRepository.findById(instance.getIdentifier())
.orElseThrow(() -> new InstanceNotFoundException(instance.getIdentifier()))
public Room pick(InstanceId instanceId, Set<EntityId> entities) throws NoRoomsAvailableException {
return containerRepository.findById(instanceId)
.orElseThrow(() -> DoesNotExistsException.forInstance(instanceId))
.pick(entities);
}
}

View File

@ -1,11 +1,12 @@
package ru.dragonestia.picker.repository.impl.container;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.room.RoomId;
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
import ru.dragonestia.picker.repository.impl.picker.RoomPicker;
import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker;
@ -19,21 +20,20 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public class InstanceContainer {
@Getter
private final Instance instance;
@Getter private final Instance instance;
private final EntityTransaction.Listener transactionListener;
private final RoomPicker picker;
@Getter private final RoomPicker picker;
private final ReadWriteLock roomLock = new ReentrantReadWriteLock();
private final Map<String, RoomContainer> rooms = new ConcurrentHashMap<>();
private final Map<RoomId, RoomContainer> rooms = new ConcurrentHashMap<>();
public InstanceContainer(@NotNull Instance instance, @NotNull EntityTransaction.Listener transactionListener) {
public InstanceContainer(Instance instance, EntityTransaction.Listener transactionListener) {
this.instance = instance;
this.transactionListener = transactionListener;
this.picker = initPicker();
}
private @NotNull RoomPicker initPicker() {
private RoomPicker initPicker() {
return switch (instance.getPickingMethod()) {
case SEQUENTIAL_FILLING -> new SequentialFillingPicker(this);
case ROUND_ROBIN -> new RoundRobinPicker(this);
@ -41,31 +41,31 @@ public class InstanceContainer {
};
}
public void addRoom(Room room) throws RoomAlreadyExistException {
public void addRoom(Room room) throws AlreadyExistsException {
roomLock.writeLock().lock();
try {
if (rooms.containsKey(room.getIdentifier())) {
throw new RoomAlreadyExistException(instance.getIdentifier(), room.getIdentifier());
if (rooms.containsKey(room.getId())) {
throw AlreadyExistsException.forRoom(instance.getId(), room.getId());
}
var container = new RoomContainer(room, this);
rooms.put(room.getIdentifier(), container);
rooms.put(room.getId(), container);
picker.add(container);
} finally {
roomLock.writeLock().unlock();
}
}
public void removeRoom(@NotNull Room room) {
public void removeRoom(RoomId roomId) {
roomLock.writeLock().lock();
try {
picker.remove(rooms.remove(room.getIdentifier()));
picker.remove(rooms.remove(roomId));
} finally {
roomLock.writeLock().unlock();
}
}
public void removeRoomsByIds(@NotNull Collection<String> roomIds) {
public void removeRoomsByIds(Collection<RoomId> roomIds) {
roomLock.writeLock().lock();
try {
roomIds.forEach(roomId -> picker.remove(rooms.remove(roomId)));
@ -74,7 +74,7 @@ public class InstanceContainer {
}
}
public @NotNull Optional<RoomContainer> findRoomById(@NotNull String roomId) {
public Optional<RoomContainer> findRoomById(RoomId roomId) {
roomLock.readLock().lock();
try {
return Optional.ofNullable(rooms.get(roomId));
@ -83,7 +83,7 @@ public class InstanceContainer {
}
}
public @NotNull Collection<RoomContainer> allRooms() {
public Collection<RoomContainer> allRooms() {
roomLock.readLock().lock();
try {
return rooms.values();
@ -92,16 +92,16 @@ public class InstanceContainer {
}
}
public @NotNull Room pick(@NotNull Set<Entity> entities) {
public Room pick(Set<EntityId> entities) {
var entitiesObj = entities.stream()
.map(Entity::new)
.toList(); // TODO: find entities
synchronized (picker) {
var room = picker.pick(entities);
room.addEntities(entities, false);
transactionListener.accept(new EntityTransaction(room.getRoom(), entities));
var room = picker.pick(entitiesObj);
room.addEntities(entitiesObj, false);
transactionListener.accept(new EntityTransaction(room.getRoom(), entitiesObj));
return room.getRoom();
}
}
public @NotNull RoomPicker getPicker() {
return picker;
}
}

View File

@ -2,7 +2,7 @@ package ru.dragonestia.picker.repository.impl.container;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.exception.RoomAreFullException;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
@ -32,7 +32,7 @@ public class RoomContainer {
entities.addAll(toAdd);
noticePickersAboutEntityNumberUpdate();
} else {
throw new RoomAreFullException(room.getInstanceIdentifier(), room.getIdentifier());
throw new RoomAreFullException(room.getInstance().getId(), room.getId());
}
} finally {
entityLock.writeLock().unlock();
@ -75,7 +75,7 @@ public class RoomContainer {
}
private boolean canAdd0(int entities) {
return room.hasUnlimitedSlots() || entities + countEntities() <= room.getMaxSlots();
return room.getSlots() == -1 || entities + countEntities() <= room.getSlots();
}
public boolean canAdd(int entities) {

View File

@ -1,8 +1,8 @@
package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
@ -27,7 +27,7 @@ public class LeastPickedPicker implements RoomPicker {
@Override
public void remove(RoomContainer container) {
synchronized (map) {
map.removeById(container.getRoom().getIdentifier());
map.removeById(container.getRoom().getId().getValue());
}
}
@ -41,7 +41,7 @@ public class LeastPickedPicker implements RoomPicker {
if (!wrapper.canAddUnits(entities.size())) throw new RuntimeException();
} catch (RuntimeException ex) {
throw new NoRoomsAvailableException(container.getInstance().getIdentifier());
throw new NoRoomsAvailableException(container.getInstance().getId());
}
}
@ -50,7 +50,7 @@ public class LeastPickedPicker implements RoomPicker {
public void updateEntitiesAmount(Room room, int users) {
synchronized (map) {
map.updateItem(room.getIdentifier(), prevValue -> users);
map.updateItem(room.getId().getValue(), prevValue -> users);
}
}

View File

@ -1,7 +1,7 @@
package ru.dragonestia.picker.repository.impl.picker;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
public interface RoomPicker extends Picker<RoomContainer, Entity> {

View File

@ -17,7 +17,7 @@ public class RoomWrapper implements ItemWrapper<RoomContainer>, QueuedLinkedList
@Override
public String getId() {
return container.getRoom().getIdentifier();
return container.getRoom().getId().getValue();
}
@Override
@ -27,7 +27,7 @@ public class RoomWrapper implements ItemWrapper<RoomContainer>, QueuedLinkedList
@Override
public int maxUnits() {
return container.getRoom().getMaxSlots();
return container.getRoom().getSlots();
}
@Override

View File

@ -1,9 +1,9 @@
package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.repository.impl.collection.QueuedLinkedList;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
@ -28,7 +28,7 @@ public class RoundRobinPicker implements RoomPicker {
@Override
public void remove(RoomContainer container) {
synchronized (list) {
list.removeById(container.getRoom().getIdentifier());
list.removeById(container.getRoom().getId().getValue());
}
}
@ -42,7 +42,7 @@ public class RoundRobinPicker implements RoomPicker {
addition.set(amount);
wrapper = list.pick();
} catch (RuntimeException ex) {
throw new NoRoomsAvailableException(container.getInstance().getIdentifier());
throw new NoRoomsAvailableException(container.getInstance().getId());
}
}

View File

@ -1,9 +1,9 @@
package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
import ru.dragonestia.picker.repository.impl.container.RoomContainer;
@ -20,14 +20,14 @@ public class SequentialFillingPicker implements RoomPicker {
@Override
public void add(RoomContainer container) {
synchronized (wrappers) {
wrappers.put(container.getRoom().getIdentifier(), new RoomWrapper(container));
wrappers.put(container.getRoom().getId().getValue(), new RoomWrapper(container));
}
}
@Override
public void remove(RoomContainer container) {
synchronized (wrappers) {
wrappers.remove(container.getRoom().getIdentifier());
wrappers.remove(container.getRoom().getId());
}
}
@ -43,7 +43,7 @@ public class SequentialFillingPicker implements RoomPicker {
}
}
throw new NoRoomsAvailableException(container.getInstance().getIdentifier());
throw new NoRoomsAvailableException(container.getInstance().getId());
}
@Override

View File

@ -1,10 +1,10 @@
package ru.dragonestia.picker.service;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import ru.dragonestia.picker.model.account.Account;
import ru.dragonestia.picker.model.account.AccountId;
import java.util.Collection;
import java.util.Optional;
@ -12,18 +12,18 @@ import java.util.Optional;
public interface AccountService extends UserDetailsService {
@PreAuthorize("hasRole('ADMIN')")
@NotNull Account createNewAccount(@NotNull String username, @NotNull String password);
Account createNewAccount(AccountId id, String password);
@NotNull Optional<Account> findAccount(@NotNull String accountId);
Optional<Account> findAccount(String accountId);
@PreAuthorize("hasRole('ADMIN')")
@NotNull Collection<Account> allAccounts();
Collection<Account> allAccounts();
@PreAuthorize("hasRole('ADMIN')")
void removeAccount(@NotNull Account account);
void removeAccount(Account account);
@PreAuthorize("hasRole('ADMIN') || principal.username.equals(account.username)")
void updateState(@NotNull Account account);
void updateState(Account account);
@Override
Account loadUserByUsername(String username) throws UsernameNotFoundException;

View File

@ -1,7 +1,7 @@
package ru.dragonestia.picker.service;
import ru.dragonestia.picker.api.exception.RoomAreFullException;
import ru.dragonestia.picker.api.model.user.ResponseUser;
import ru.dragonestia.picker.exception.RoomAreFullException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
@ -10,15 +10,13 @@ import java.util.List;
public interface EntityService {
Collection<Room> getEntityRooms(Entity entity);
Collection<Room> getEntityRooms(EntityId id);
void linkEntitiesWithRoom(Room room, Collection<Entity> entities, boolean force) throws RoomAreFullException;
void linkEntitiesWithRoom(Room room, Collection<EntityId> entities, boolean force) throws RoomAreFullException;
void unlinkEntitiesFromRoom(Room room, Collection<Entity> entities);
void unlinkEntitiesFromRoom(Room room, Collection<EntityId> entities);
Collection<Entity> getRoomEntities(Room room);
List<ResponseUser> searchEntities(String input);
ResponseUser getEntityDetails(String userId);
List<Entity> searchEntities(EntityId input);
}

View File

@ -1,9 +1,10 @@
package ru.dragonestia.picker.service;
import org.springframework.security.access.prepost.PreAuthorize;
import ru.dragonestia.picker.api.exception.InvalidInstanceIdentifierException;
import ru.dragonestia.picker.api.exception.InstanceAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import java.util.List;
import java.util.Optional;
@ -11,12 +12,12 @@ import java.util.Optional;
public interface InstanceService {
@PreAuthorize("hasRole('NODE_MANAGEMENT')")
void create(Instance instance) throws InvalidInstanceIdentifierException, InstanceAlreadyExistException;
void create(Instance instance) throws InvalidIdentifierException, AlreadyExistsException;
@PreAuthorize("hasRole('NODE_MANAGEMENT')")
void remove(Instance instance);
List<Instance> all();
Optional<Instance> find(String nodeId);
Optional<Instance> find(InstanceId id);
}

View File

@ -1,11 +1,11 @@
package ru.dragonestia.picker.service;
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.api.repository.response.PickedRoomResponse;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.room.RoomId;
import java.util.Collection;
import java.util.Optional;
@ -13,15 +13,15 @@ import java.util.Set;
public interface RoomService {
void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException;
void create(Room room) throws InvalidIdentifierException, AlreadyExistsException;
void remove(Room room);
Optional<Room> find(Instance instance, String roomId);
Optional<Room> find(InstanceId instanceId, RoomId roomId);
Collection<Room> all(Instance instance);
Collection<Room> all(InstanceId instanceId);
PickedRoomResponse pickAvailable(Instance instance, Set<Entity> entities);
Room pick(InstanceId instanceId, Set<EntityId> entities);
void updateState(Room room);
}

View File

@ -2,13 +2,13 @@ package ru.dragonestia.picker.service.impl;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.exception.ConstantAdminParamsException;
import ru.dragonestia.picker.config.RoomPickerServerConfig;
import ru.dragonestia.picker.exception.AdminAccountMutationException;
import ru.dragonestia.picker.model.account.Account;
import ru.dragonestia.picker.model.account.AccountId;
import ru.dragonestia.picker.model.account.Permission;
import ru.dragonestia.picker.service.AccountService;
@ -30,39 +30,39 @@ public class AccountServiceImpl implements AccountService {
@PostConstruct
void init() {
var account = createNewAccount(adminCredentials.username(), adminCredentials.password());
var account = createNewAccount(AccountId.of(adminCredentials.username()), adminCredentials.password());
account.setAuthorities(Arrays.stream(Permission.values()).collect(Collectors.toSet()));
createNewAccount("test", "qwerty123");
createNewAccount(AccountId.of("test"), "qwerty123");
}
public @NotNull Account createNewAccount(@NotNull String username, @NotNull String password) {
var account = new Account(username, passwordEncoder.encode(password));
public Account createNewAccount(AccountId id, String password) {
var account = new Account(id, passwordEncoder.encode(password));
accounts.put(account.getUsername().toLowerCase(), account);
return account;
}
@Override
public @NotNull Optional<Account> findAccount(@NotNull String accountId) {
public Optional<Account> findAccount(String accountId) {
return Optional.ofNullable(accounts.getOrDefault(accountId, null));
}
@Override
public @NotNull Collection<Account> allAccounts() {
public Collection<Account> allAccounts() {
return accounts.values().stream()
.filter(account -> !adminCredentials.username().equals(account.getUsername()))
.toList();
}
@Override
public void removeAccount(@NotNull Account account) {
public void removeAccount(Account account) {
checkAdmin(account.getUsername());
accounts.remove(account.getUsername());
account.setEnabled(false);
}
@Override
public void updateState(@NotNull Account account) {
public void updateState(Account account) {
checkAdmin(account.getUsername());
// TODO: save data to local storage
}
@ -79,7 +79,7 @@ public class AccountServiceImpl implements AccountService {
private void checkAdmin(String accountId) {
if (adminCredentials.username().equals(accountId)) {
throw new ConstantAdminParamsException();
throw new AdminAccountMutationException();
}
}
}

View File

@ -2,7 +2,7 @@ package ru.dragonestia.picker.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.model.user.ResponseUser;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.EntityRepository;
@ -17,17 +17,17 @@ public class EntityServiceImpl implements EntityService {
private final EntityRepository entityRepository;
@Override
public Collection<Room> getEntityRooms(Entity entity) {
return entityRepository.findAllLinkedEntityRooms(entity);
public Collection<Room> getEntityRooms(EntityId id) {
return entityRepository.findAllLinkedEntityRooms(id);
}
@Override
public void linkEntitiesWithRoom(Room room, Collection<Entity> entities, boolean force) {
public void linkEntitiesWithRoom(Room room, Collection<EntityId> entities, boolean force) {
entityRepository.linkWithRoom(room, entities, force);
}
@Override
public void unlinkEntitiesFromRoom(Room room, Collection<Entity> entities) {
public void unlinkEntitiesFromRoom(Room room, Collection<EntityId> entities) {
entityRepository.unlinkWithRoom(room, entities);
}
@ -37,12 +37,7 @@ public class EntityServiceImpl implements EntityService {
}
@Override
public List<ResponseUser> searchEntities(String input) {
return entityRepository.search(input).stream().map(Entity::toResponseObject).toList();
}
@Override
public ResponseUser getEntityDetails(String userId) {
throw new UnsupportedOperationException("Not implemented");
public List<Entity> searchEntities(EntityId input) {
return entityRepository.search(input).stream().toList();
}
}

View File

@ -2,9 +2,10 @@ package ru.dragonestia.picker.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.exception.InvalidInstanceIdentifierException;
import ru.dragonestia.picker.api.exception.InstanceAlreadyExistException;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.service.InstanceService;
@ -22,18 +23,18 @@ public class InstanceServiceImpl implements InstanceService {
private final InstanceAndRoomStorage storage;
@Override
public void create(Instance instance) throws InvalidInstanceIdentifierException, InstanceAlreadyExistException {
public void create(Instance instance) throws InvalidIdentifierException, AlreadyExistsException {
instanceRepository.create(instance);
storage.saveInstance(instance);
}
@Override
public void remove(Instance instance) {
for (var room: roomRepository.all(instance)) {
for (var room: roomRepository.all(instance.getId())) {
storage.removeRoom(room);
}
instanceRepository.delete(instance);
instanceRepository.delete(instance.getId());
storage.removeInstance(instance);
}
@ -43,7 +44,7 @@ public class InstanceServiceImpl implements InstanceService {
}
@Override
public Optional<Instance> find(String nodeId) {
return instanceRepository.findById(nodeId);
public Optional<Instance> find(InstanceId id) {
return instanceRepository.findById(id);
}
}

View File

@ -3,14 +3,14 @@ package ru.dragonestia.picker.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException;
import ru.dragonestia.picker.api.exception.InstanceNotFoundException;
import ru.dragonestia.picker.api.exception.NotPersistedNodeException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException;
import ru.dragonestia.picker.api.repository.response.PickedRoomResponse;
import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.exception.ConflictingPersistParametersException;
import ru.dragonestia.picker.exception.DoesNotExistsException;
import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.room.RoomId;
import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.EntityRepository;
@ -18,7 +18,6 @@ import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.storage.InstanceAndRoomStorage;
import java.util.*;
import java.util.stream.Collectors;
@Log4j2
@Service
@ -31,10 +30,12 @@ public class RoomServiceImpl implements RoomService {
private final InstanceAndRoomStorage storage;
@Override
public void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException, NotPersistedNodeException {
var node = instanceRepository.findById(room.getInstanceIdentifier()).orElseThrow(() -> new InstanceNotFoundException(room.getInstanceIdentifier()));
if (!node.isPersist() && room.isPersist()) {
throw new NotPersistedNodeException(node.getIdentifier(), room.getIdentifier());
public void create(Room room) throws InvalidIdentifierException, AlreadyExistsException, ConflictingPersistParametersException {
var instance = instanceRepository.findById(room.getInstance().getId())
.orElseThrow(() -> DoesNotExistsException.forInstance(room.getInstance().getId()));
if (!instance.isPersist() && room.isPersist()) {
throw ConflictingPersistParametersException.forRoom(instance.getId(), room.getId());
}
roomRepository.create(room);
@ -43,34 +44,23 @@ public class RoomServiceImpl implements RoomService {
@Override
public void remove(Room room) {
roomRepository.remove(room);
roomRepository.remove(room.getInstance().getId(), room.getId());
storage.removeRoom(room);
}
@Override
public Optional<Room> find(Instance instance, String roomId) {
return roomRepository.find(instance, roomId);
public Optional<Room> find(InstanceId instanceId, RoomId roomId) {
return roomRepository.find(instanceId, roomId);
}
@Override
public Collection<Room> all(Instance instance) {
return roomRepository.all(instance);
public Collection<Room> all(InstanceId instanceId) {
return roomRepository.all(instanceId);
}
@Override
public PickedRoomResponse pickAvailable(Instance instance, Set<Entity> entities) {
var room = roomRepository.pick(instance, entities);
var roomUsers = entityRepository.entitiesOf(room);
return new PickedRoomResponse(
room.getInstanceIdentifier(),
room.getIdentifier(),
room.getPayload(),
room.getMaxSlots(),
roomUsers.size(),
room.isLocked(),
roomUsers.stream().map(Entity::getIdentifier).collect(Collectors.toSet())
);
public Room pick(InstanceId instanceId, Set<EntityId> entities) {
return roomRepository.pick(instanceId, entities);
}
@Override

View File

@ -66,7 +66,7 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override
public void saveInstance(Instance instance) {
if (!instance.isPersist()) return;
var instanceFile = new File(path + "/instances/" + instance.getIdentifier() + ".json");
var instanceFile = new File(path + "/instances/" + instance.getId() + ".json");
var writer = objectMapper.writer();
try {
@ -79,16 +79,16 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override
public void removeInstance(Instance instance) {
if (!instance.isPersist()) return;
new File(path + "/nodes/" + instance.getIdentifier() + ".json").delete();
new File(path + "/nodes/" + instance.getId() + ".json").delete();
log.info("Removed instance '%s' from disk storage".formatted(instance.getIdentifier()));
log.info("Removed instance '%s' from disk storage".formatted(instance.getId()));
}
@SneakyThrows
@Override
public void saveRoom(Room room) {
if (!room.isPersist()) return;
var roomFile = new File("%s/rooms/%s.%s.json".formatted(path, room.getInstanceIdentifier(), room.getIdentifier()));
var roomFile = new File("%s/rooms/%s.%s.json".formatted(path, room.getInstance().getId(), room.getId()));
var writer = objectMapper.writer();
try {
@ -101,8 +101,8 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override
public void removeRoom(Room room) {
if (!room.isPersist()) return;
new File("%s/rooms/%s.%s.json".formatted(path, room.getInstanceIdentifier(), room.getIdentifier())).delete();
new File("%s/rooms/%s.%s.json".formatted(path, room.getInstance().getId(), room.getId())).delete();
log.info("Removed room '%s/%s' from disk storage".formatted(room.getInstanceIdentifier(), room.getIdentifier()));
log.info("Removed room '%s/%s' from disk storage".formatted(room.getInstance().getId(), room.getId()));
}
}

View File

@ -9,7 +9,7 @@ import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.factory.RoomFactory;
import ru.dragonestia.picker.model.room.factory.RoomFactory;
import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.EntityRepository;

View File

@ -48,8 +48,8 @@ public class LeastPickedTests {
var users = entityRepository.entitiesOf(room);
Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getIdentifier(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
Assertions.assertEquals(expectedRoomId, room.getIdentifier());
System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getId(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
Assertions.assertEquals(expectedRoomId, room.getId());
}
public static class PickingArgumentProvider implements ArgumentsProvider {

View File

@ -44,7 +44,7 @@ public class RoundRobinTests {
var users = entityRepository.entitiesOf(room);
Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
Assertions.assertEquals(expectedRoomId, room.getIdentifier());
Assertions.assertEquals(expectedRoomId, room.getId());
}
public static class PickingArgumentProvider implements ArgumentsProvider {

View File

@ -48,8 +48,8 @@ public class SequentialFillingTests {
var users = entityRepository.entitiesOf(room);
Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation
System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getIdentifier(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
Assertions.assertEquals(expectedRoomId, room.getIdentifier());
System.out.printf("Room(%s) has %s/%s users. Expected: %s(%s), added: %s%n", room.getId(), users.size(), slots, expectedRoomId, expectedRoomUsers, usersAmount);
Assertions.assertEquals(expectedRoomId, room.getId());
}
public static class PickingArgumentProvider implements ArgumentsProvider {

View File

@ -24,12 +24,12 @@ public class InstanceServiceTests {
var node = new Instance(NodeIdentifier.of("test"), PickingMethod.SEQUENTIAL_FILLING, false);
Assertions.assertDoesNotThrow(() -> instanceService.create(node));
Assertions.assertTrue(instanceService.find(node.getIdentifier()).isPresent());
Assertions.assertTrue(instanceService.find(node.getId()).isPresent());
Assertions.assertThrows(InstanceAlreadyExistException.class, () -> instanceService.create(node));
instanceService.remove(node);
Assertions.assertFalse(() -> instanceService.find(node.getIdentifier()).isPresent());
Assertions.assertFalse(() -> instanceService.find(node.getId()).isPresent());
}
@WithMockUser(roles = {"NODE_MANAGEMENT"})

View File

@ -17,7 +17,7 @@ import ru.dragonestia.picker.api.repository.type.RoomIdentifier;
import ru.dragonestia.picker.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.model.factory.RoomFactory;
import ru.dragonestia.picker.model.room.factory.RoomFactory;
import java.util.List;
import java.util.Set;
@ -51,12 +51,12 @@ public class RoomServiceTests {
var room = roomFactory.create(RoomIdentifier.of("test-room"), instance, IRoom.UNLIMITED_SLOTS, "", false);
roomService.create(room);
Assertions.assertTrue(roomService.find(instance, room.getIdentifier()).isPresent());
Assertions.assertTrue(roomService.find(instance, room.getId()).isPresent());
Assertions.assertThrows(RoomAlreadyExistException.class, () -> roomService.create(room));
roomService.remove(room);
Assertions.assertFalse(roomService.find(instance, room.getIdentifier()).isPresent());
Assertions.assertFalse(roomService.find(instance, room.getId()).isPresent());
}
@WithMockUser(roles = {"NODE_MANAGEMENT"})
@ -107,7 +107,7 @@ public class RoomServiceTests {
);
Assertions.assertEquals("test-room4", roomService.pickAvailable(instance, users).roomId());
Assertions.assertEquals("test-room4", roomService.pick(instance, users).roomId());
}
@WithMockUser(roles = {"NODE_MANAGEMENT"})
@ -127,6 +127,6 @@ public class RoomServiceTests {
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.create(room));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.remove(room));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.find(node, "Bruh"));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.pickAvailable(node, Set.of(new Entity(EntityIdentifier.of("1")))));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.pick(node, Set.of(new Entity(EntityIdentifier.of("1")))));
}
}