!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 { dependencies {
implementation project(":client-api") //implementation project(":client-api")
developmentOnly("org.springframework.boot:spring-boot-devtools") developmentOnly("org.springframework.boot:spring-boot-devtools")
implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-security' 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") @After(value = "execution(void ru.dragonestia.picker.repository.InstanceRepository.create(ru.dragonestia.picker.model.instance.Instance)) && args(instance)", argNames = "instance")
void onCreateNode(Instance instance) { void onCreateNode(Instance instance) {
var nodeId = instance.getIdentifier(); var nodeId = instance.getId();
var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId).users()) var gauge = Gauge.builder("roompicker_node_users_total", () -> data.get(nodeId.getValue()).users())
.tag("nodeId", nodeId) .tag("nodeId", nodeId.getValue())
.register(meterRegistry); .register(meterRegistry);
var counter = Counter.builder("roompicker_picks") var counter = Counter.builder("roompicker_picks")
.tag("nodeId", nodeId) .tag("nodeId", nodeId.getValue())
.baseUnit("1s") .baseUnit("1s")
.register(meterRegistry); .register(meterRegistry);
var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId).locked()) var lockedGauge = Gauge.builder("roompicker_locked_rooms", () -> data.get(nodeId.getValue()).locked())
.tag("nodeId", nodeId) .tag("nodeId", nodeId.getValue())
.register(meterRegistry); .register(meterRegistry);
var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance).size()) var roomsGauge = Gauge.builder("roompicker_rooms", () -> roomRepository.all(instance.getId()).size())
.tag("nodeId", nodeId) .tag("nodeId", nodeId.getValue())
.register(meterRegistry); .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") @After(value = "execution(* ru.dragonestia.picker.repository.InstanceRepository.delete(ru.dragonestia.picker.model.instance.Instance)) && args(instance)", argNames = "instance")
void onDeleteNode(Instance 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.usersGauge());
meterRegistry.remove(data.picksPerMinute()); 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") @AfterReturning(value = "execution(* ru.dragonestia.picker.repository.RoomRepository.pick(ru.dragonestia.picker.model.instance.Instance, *)) && args(instance, ..)", argNames = "instance")
void onPickRoom(Instance instance) { void onPickRoom(Instance instance) {
data.get(instance.getIdentifier()).picksPerMinute().increment(); data.get(instance.getId().getValue()).picksPerMinute().increment();
} }
@Scheduled(fixedDelay = 3_000) @Scheduled(fixedDelay = 3_000)
void updateUserMetrics() { void updateUserMetrics() {
entityRepository.countEntitiesForNodes().forEach((nodeId, users) -> { entityRepository.countEntitiesForInstances().forEach((nodeId, users) -> {
Optional.ofNullable(data.get(nodeId)).ifPresent(node -> node.users().set(users)); Optional.ofNullable(data.get(nodeId)).ifPresent(node -> node.users().set(users));
}); });
containerRepository.all().forEach(nodeContainer -> { containerRepository.all().forEach(nodeContainer -> {
var locked = data.get(nodeContainer.getInstance().getIdentifier()).locked(); var locked = data.get(nodeContainer.getInstance().getId().getValue()).locked();
locked.set(0); locked.set(0);
nodeContainer.allRooms().forEach(roomContainer -> { nodeContainer.allRooms().forEach(roomContainer -> {

View File

@ -10,15 +10,14 @@ import org.springframework.context.annotation.Profile;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ru.dragonestia.picker.api.model.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.interceptor.DebugInterceptor;
import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity; 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.RoomRepository;
import ru.dragonestia.picker.repository.InstanceRepository; import ru.dragonestia.picker.repository.InstanceRepository;
import ru.dragonestia.picker.repository.EntityRepository; import ru.dragonestia.picker.repository.EntityRepository;
@ -46,9 +45,9 @@ public class TestConfig implements WebMvcConfigurer {
@Bean @Bean
void createInstances() { void createInstances() {
createInstanceWithContent(new Instance(NodeIdentifier.of("game-servers"), PickingMethod.ROUND_ROBIN, false)); createInstanceWithContent(new Instance(InstanceId.of("game-servers"), PickingMethod.ROUND_ROBIN, false));
createInstanceWithContent(new Instance(NodeIdentifier.of("game-lobbies"), PickingMethod.LEAST_PICKED, false)); createInstanceWithContent(new Instance(InstanceId.of("game-lobbies"), PickingMethod.LEAST_PICKED, false));
createInstanceWithContent(new Instance(NodeIdentifier.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false)); createInstanceWithContent(new Instance(InstanceId.of("hub"), PickingMethod.SEQUENTIAL_FILLING, false));
} }
@SneakyThrows @SneakyThrows
@ -58,17 +57,17 @@ public class TestConfig implements WebMvcConfigurer {
for (int i = 1; i <= 5; i++) { for (int i = 1; i <= 5; i++) {
var slots = 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); roomRepository.create(room);
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) { 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); entityRepository.linkWithRoom(room, List.of(user), false);
} }
} }
for (int i = 0; i < 5; i++) { 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); room.setLocked((i & 1) == 0);
roomRepository.create(room); roomRepository.create(room);
} }

View File

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

View File

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

View File

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

View File

@ -1,85 +1,9 @@
package ru.dragonestia.picker.controller; 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 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 @RestControllerAdvice
public class ExceptionHandlerController { public class ExceptionHandlerController {
@ExceptionHandler(InstanceNotFoundException.class) // TODO
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));
}
} }

View File

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

View File

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

View File

@ -1,16 +1,12 @@
package ru.dragonestia.picker.controller; 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.GetMapping;
import org.springframework.web.bind.annotation.RestController; 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 @RestController
public class RoomPickerController { public class RoomPickerController {
@Operation(summary = "Server info")
@GetMapping("/info") @GetMapping("/info")
RoomPickerInfoResponse info() { RoomPickerInfoResponse info() {
return new RoomPickerInfoResponse("v0.0.1"); 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.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller; 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.ObjectInstance;
import ru.dragonestia.picker.controller.graphql.object.ObjectRoom; import ru.dragonestia.picker.controller.graphql.object.ObjectRoom;
import ru.dragonestia.picker.controller.graphql.object.ObjectEntity; import ru.dragonestia.picker.controller.graphql.object.ObjectEntity;
import ru.dragonestia.picker.controller.graphql.object.type.DataProvider; import ru.dragonestia.picker.controller.graphql.object.type.DataProvider;
import ru.dragonestia.picker.model.entity.Entity; 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.InstanceService;
import ru.dragonestia.picker.service.RoomService; import ru.dragonestia.picker.service.RoomService;
import ru.dragonestia.picker.service.EntityService; import ru.dragonestia.picker.service.EntityService;
@ -40,40 +42,40 @@ public class GraphqlController {
@QueryMapping @QueryMapping
ObjectInstance instanceById(@Argument String id) { ObjectInstance instanceById(@Argument String id) {
return instanceService.find(id) return instanceService.find(InstanceId.of(id))
.map(node -> new ObjectInstance(node, dataProvider)) .map(node -> new ObjectInstance(node, dataProvider))
.orElse(null); .orElse(null);
} }
@QueryMapping @QueryMapping
List<ObjectRoom> allRooms(@NotNull String nodeId) { 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; if (node == null) return null;
return roomService.all(node).stream() return roomService.all(node.getId()).stream()
.map(room -> new ObjectRoom(room, dataProvider)) .map(room -> new ObjectRoom(room, dataProvider))
.toList(); .toList();
} }
@QueryMapping @QueryMapping
ObjectRoom roomById(@Argument String nodeId, @NotNull String roomId) { 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; if (node == null) return null;
return roomService.find(node, roomId) return roomService.find(node.getId(), RoomId.of(roomId))
.map(room -> new ObjectRoom(room, dataProvider)) .map(room -> new ObjectRoom(room, dataProvider))
.orElse(null); .orElse(null);
} }
@QueryMapping @QueryMapping
ObjectEntity entityById(@Argument String id) { ObjectEntity entityById(@Argument String id) {
return new ObjectEntity(new Entity(EntityIdentifier.of(id)), dataProvider); return new ObjectEntity(new Entity(EntityId.of(id)), dataProvider);
} }
@QueryMapping @QueryMapping
List<ObjectEntity> searchEntity(@Argument String input) { List<ObjectEntity> searchEntity(@Argument String input) {
return entityService.searchEntities(input).stream() return entityService.searchEntities(EntityId.of(input)).stream()
.map(user -> new ObjectEntity(new Entity(user.getIdentifierObject()), dataProvider)) .map(user -> new ObjectEntity(new Entity(user.getId()), dataProvider))
.toList(); .toList();
} }
} }

View File

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

View File

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

View File

@ -14,21 +14,21 @@ public class ObjectRoom {
private List<ObjectEntity> cachedUsers = null; private List<ObjectEntity> cachedUsers = null;
public String getId() { public String getId() {
return room.getIdentifier(); return room.getId().getValue();
} }
public String getInstanceId() { public String getInstanceId() {
return room.getInstanceIdentifier(); return room.getInstance().getId().getValue();
} }
public ObjectInstance getInstance() { public ObjectInstance getInstance() {
return dataProvider.instanceService().find(room.getInstanceIdentifier()) return dataProvider.instanceService().find(room.getInstance().getId())
.map(node -> new ObjectInstance(node, dataProvider)) .map(node -> new ObjectInstance(node, dataProvider))
.orElseThrow(); .orElseThrow();
} }
public int getSlots() { public int getSlots() {
return room.getMaxSlots(); return room.getSlots();
} }
public String getPayload() { 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; package ru.dragonestia.picker.model.account;
import org.jetbrains.annotations.Contract; import lombok.Getter;
import org.jetbrains.annotations.NotNull; import lombok.Setter;
import org.springframework.security.core.userdetails.UserDetails; 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.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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 final String lowerUsername;
private String password; @Getter @Setter private String password;
private Set<Permission> permissions = new HashSet<>(); private Set<Permission> permissions = new HashSet<>();
private boolean locked = false; @Getter @Setter private boolean locked = false;
private boolean enabled = true; @Getter @Setter private boolean enabled = true;
public Account(@NotNull String username, @NotNull String password) { public Account(AccountId id, String password) {
this.username = username; this.id = id;
this.username = id.getValue();
this.lowerUsername = username.toLowerCase(); this.lowerUsername = username.toLowerCase();
this.password = password; this.password = password;
} }
@Override @Override
public @NotNull Collection<Permission> getAuthorities() { public Collection<Permission> getAuthorities() {
return permissions; return permissions;
} }
@Override public void setAuthorities(Set<Permission> permissions) {
public boolean isLocked() {
return locked;
}
@Contract("_ -> this")
public @NotNull Account setAuthorities(@NotNull Set<Permission> permissions) {
this.permissions = 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 @Override
@ -73,28 +44,11 @@ public class Account implements IAccount, UserDetails {
return !locked; return !locked;
} }
@Contract("_ -> this")
public @NotNull Account setLocked(boolean value) {
locked = value;
return this;
}
@Override @Override
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return true; return true;
} }
@Override
public boolean isEnabled() {
return enabled;
}
@Contract("_ -> this")
public @NotNull Account setEnabled(boolean value) {
enabled = value;
return this;
}
@Override @Override
public int hashCode() { public int hashCode() {
return lowerUsername.hashCode(); return lowerUsername.hashCode();
@ -109,8 +63,4 @@ public class Account implements IAccount, UserDetails {
} }
return false; 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; package ru.dragonestia.picker.model.entity;
import org.jetbrains.annotations.NotNull; import lombok.Getter;
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;
public class Entity implements IUser { @Getter
public class Entity {
private final String identifier; private final EntityId id;
public Entity(@NotNull EntityIdentifier identifier) { public Entity(EntityId id) {
this.identifier = identifier.getValue(); this.id = id;
}
@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);
} }
@Override @Override
public String toString() { public String toString() {
return identifier; return id.getValue();
} }
@Override @Override
public int hashCode() { public int hashCode() {
return identifier.hashCode(); return id.hashCode();
} }
@Override @Override
@ -44,7 +26,7 @@ public class Entity implements IUser {
if (object == this) return true; if (object == this) return true;
if (object == null) return false; if (object == null) return false;
if (object instanceof Entity other) { if (object instanceof Entity other) {
return identifier.equals(other.identifier); return id.equals(other.id);
} }
return false; 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; package ru.dragonestia.picker.model.instance;
import org.jetbrains.annotations.NotNull; import lombok.Getter;
import org.jetbrains.annotations.Nullable; import ru.dragonestia.picker.model.instance.type.PickingMethod;
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;
public class Instance implements INode { @Getter
public class Instance {
private final String identifier; private final InstanceId id;
private final PickingMethod pickingMethod; private final PickingMethod pickingMethod;
private final boolean persist; private final boolean persist;
public Instance(@NotNull NodeIdentifier identifier, @NotNull PickingMethod pickingMethod, boolean persist) { public Instance(InstanceId id, PickingMethod pickingMethod, boolean persist) {
this.identifier = identifier.getValue(); this.id = id;
this.pickingMethod = pickingMethod; this.pickingMethod = pickingMethod;
this.persist = persist; 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 @Override
public int hashCode() { public int hashCode() {
return identifier.hashCode(); return id.hashCode();
} }
@Override @Override
@ -54,13 +26,13 @@ public class Instance implements INode {
if (object == this) return true; if (object == this) return true;
if (object == null) return false; if (object == null) return false;
if (object instanceof Instance other) { if (object instanceof Instance other) {
return identifier.equals(other.identifier); return id.equals(other.id);
} }
return false; return false;
} }
@Override @Override
public String toString() { 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; package ru.dragonestia.picker.model.room;
import org.jetbrains.annotations.NotNull; import lombok.Getter;
import org.jetbrains.annotations.Nullable; import lombok.Setter;
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 ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.Instance;
import java.util.Objects; import java.util.Objects;
public class Room implements IRoom { @Getter
public class Room {
private final String identifier; private final RoomId id;
private final String instanceIdentifier; private final Instance instance;
private final int slots; private final int slots;
private final String payload; private final String payload;
private final boolean persist; 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) { public Room(RoomId id, Instance instance, int slots, String payload, boolean persist) {
this.identifier = identifier.getValue(); this.id = id;
this.instanceIdentifier = instance.getIdentifier(); this.instance = instance;
this.slots = slots; this.slots = slots;
this.payload = payload; this.payload = payload;
this.persist = persist; 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 @Override
public int hashCode() { public int hashCode() {
return Objects.hash(identifier, instanceIdentifier); return Objects.hash(id, instance.getId());
} }
@Override @Override
@ -91,7 +34,7 @@ public class Room implements IRoom {
if (object == this) return true; if (object == this) return true;
if (object == null) return false; if (object == null) return false;
if (object instanceof Room other) { 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; 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; 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.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.entity.Entity;
@ -9,17 +10,17 @@ import java.util.Map;
public interface EntityRepository { 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> entitiesOf(Room room);
Collection<Entity> search(String input); Collection<Entity> search(EntityId input);
int countAllEntities(); int countAllEntities();
Map<String, Integer> countEntitiesForNodes(); Map<String, Integer> countEntitiesForInstances();
} }

View File

@ -1,18 +1,19 @@
package ru.dragonestia.picker.repository; 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.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface InstanceRepository { 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(); List<Instance> all();
} }

View File

@ -1,10 +1,11 @@
package ru.dragonestia.picker.repository; package ru.dragonestia.picker.repository;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.model.instance.Instance; 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.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.room.RoomId;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
@ -12,13 +13,13 @@ import java.util.Set;
public interface RoomRepository { 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.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component; 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.Instance;
import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer; import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
import ru.dragonestia.picker.repository.impl.type.EntityTransaction; import ru.dragonestia.picker.repository.impl.type.EntityTransaction;
@ -15,25 +16,25 @@ import java.util.concurrent.ConcurrentHashMap;
@Component @Component
public class ContainerRepository { public class ContainerRepository {
private final Map<String, InstanceContainer> containers = new ConcurrentHashMap<>(); private final Map<InstanceId, InstanceContainer> containers = new ConcurrentHashMap<>();
private EntityTransaction.Listener transactionListener = transaction -> {}; private EntityTransaction.Listener transactionListener = transaction -> {};
public void create(Instance instance) throws InstanceAlreadyExistException { public void create(Instance instance) throws AlreadyExistsException {
if (containers.containsKey(instance.getIdentifier())) { if (containers.containsKey(instance.getId())) {
throw new InstanceAlreadyExistException(instance.getIdentifier()); throw AlreadyExistsException.forInstance(instance.getId());
} }
var container = new InstanceContainer(instance, transactionListener); var container = new InstanceContainer(instance, transactionListener);
containers.put(instance.getIdentifier(), container); containers.put(instance.getId(), container);
} }
public void remove(@NotNull String instanceId) { public void remove(InstanceId id) {
containers.remove(instanceId); containers.remove(id);
} }
public @NotNull Optional<InstanceContainer> findById(@NotNull String instanceId) { public @NotNull Optional<InstanceContainer> findById(InstanceId id) {
return Optional.ofNullable(containers.get(instanceId)); return Optional.ofNullable(containers.get(id));
} }
public @NotNull Collection<InstanceContainer> all() { public @NotNull Collection<InstanceContainer> all() {

View File

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

View File

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

View File

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

View File

@ -1,11 +1,12 @@
package ru.dragonestia.picker.repository.impl.container; package ru.dragonestia.picker.repository.impl.container;
import lombok.Getter; import lombok.Getter;
import org.jetbrains.annotations.NotNull; import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.room.Room; import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity; 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.LeastPickedPicker;
import ru.dragonestia.picker.repository.impl.picker.RoomPicker; import ru.dragonestia.picker.repository.impl.picker.RoomPicker;
import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker; import ru.dragonestia.picker.repository.impl.picker.RoundRobinPicker;
@ -19,21 +20,20 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public class InstanceContainer { public class InstanceContainer {
@Getter @Getter private final Instance instance;
private final Instance instance;
private final EntityTransaction.Listener transactionListener; private final EntityTransaction.Listener transactionListener;
private final RoomPicker picker; @Getter private final RoomPicker picker;
private final ReadWriteLock roomLock = new ReentrantReadWriteLock(); 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.instance = instance;
this.transactionListener = transactionListener; this.transactionListener = transactionListener;
this.picker = initPicker(); this.picker = initPicker();
} }
private @NotNull RoomPicker initPicker() { private RoomPicker initPicker() {
return switch (instance.getPickingMethod()) { return switch (instance.getPickingMethod()) {
case SEQUENTIAL_FILLING -> new SequentialFillingPicker(this); case SEQUENTIAL_FILLING -> new SequentialFillingPicker(this);
case ROUND_ROBIN -> new RoundRobinPicker(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(); roomLock.writeLock().lock();
try { try {
if (rooms.containsKey(room.getIdentifier())) { if (rooms.containsKey(room.getId())) {
throw new RoomAlreadyExistException(instance.getIdentifier(), room.getIdentifier()); throw AlreadyExistsException.forRoom(instance.getId(), room.getId());
} }
var container = new RoomContainer(room, this); var container = new RoomContainer(room, this);
rooms.put(room.getIdentifier(), container); rooms.put(room.getId(), container);
picker.add(container); picker.add(container);
} finally { } finally {
roomLock.writeLock().unlock(); roomLock.writeLock().unlock();
} }
} }
public void removeRoom(@NotNull Room room) { public void removeRoom(RoomId roomId) {
roomLock.writeLock().lock(); roomLock.writeLock().lock();
try { try {
picker.remove(rooms.remove(room.getIdentifier())); picker.remove(rooms.remove(roomId));
} finally { } finally {
roomLock.writeLock().unlock(); roomLock.writeLock().unlock();
} }
} }
public void removeRoomsByIds(@NotNull Collection<String> roomIds) { public void removeRoomsByIds(Collection<RoomId> roomIds) {
roomLock.writeLock().lock(); roomLock.writeLock().lock();
try { try {
roomIds.forEach(roomId -> picker.remove(rooms.remove(roomId))); 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(); roomLock.readLock().lock();
try { try {
return Optional.ofNullable(rooms.get(roomId)); 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(); roomLock.readLock().lock();
try { try {
return rooms.values(); 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) { synchronized (picker) {
var room = picker.pick(entities); var room = picker.pick(entitiesObj);
room.addEntities(entities, false); room.addEntities(entitiesObj, false);
transactionListener.accept(new EntityTransaction(room.getRoom(), entities)); transactionListener.accept(new EntityTransaction(room.getRoom(), entitiesObj));
return room.getRoom(); 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 lombok.Getter;
import org.jetbrains.annotations.NotNull; 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.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker; import ru.dragonestia.picker.repository.impl.picker.LeastPickedPicker;
@ -32,7 +32,7 @@ public class RoomContainer {
entities.addAll(toAdd); entities.addAll(toAdd);
noticePickersAboutEntityNumberUpdate(); noticePickersAboutEntityNumberUpdate();
} else { } else {
throw new RoomAreFullException(room.getInstanceIdentifier(), room.getIdentifier()); throw new RoomAreFullException(room.getInstance().getId(), room.getId());
} }
} finally { } finally {
entityLock.writeLock().unlock(); entityLock.writeLock().unlock();
@ -75,7 +75,7 @@ public class RoomContainer {
} }
private boolean canAdd0(int entities) { 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) { public boolean canAdd(int entities) {

View File

@ -1,8 +1,8 @@
package ru.dragonestia.picker.repository.impl.picker; package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod; import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.model.room.Room; import ru.dragonestia.picker.model.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap; import ru.dragonestia.picker.repository.impl.collection.DynamicSortedMap;
@ -27,7 +27,7 @@ public class LeastPickedPicker implements RoomPicker {
@Override @Override
public void remove(RoomContainer container) { public void remove(RoomContainer container) {
synchronized (map) { 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(); if (!wrapper.canAddUnits(entities.size())) throw new RuntimeException();
} catch (RuntimeException ex) { } 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) { public void updateEntitiesAmount(Room room, int users) {
synchronized (map) { 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; 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.entity.Entity;
import ru.dragonestia.picker.model.instance.type.PickingMethod;
import ru.dragonestia.picker.repository.impl.container.RoomContainer; import ru.dragonestia.picker.repository.impl.container.RoomContainer;
public interface RoomPicker extends Picker<RoomContainer, Entity> { public interface RoomPicker extends Picker<RoomContainer, Entity> {

View File

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

View File

@ -1,9 +1,9 @@
package ru.dragonestia.picker.repository.impl.picker; package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.entity.Entity; 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.collection.QueuedLinkedList;
import ru.dragonestia.picker.repository.impl.container.InstanceContainer; import ru.dragonestia.picker.repository.impl.container.InstanceContainer;
import ru.dragonestia.picker.repository.impl.container.RoomContainer; import ru.dragonestia.picker.repository.impl.container.RoomContainer;
@ -28,7 +28,7 @@ public class RoundRobinPicker implements RoomPicker {
@Override @Override
public void remove(RoomContainer container) { public void remove(RoomContainer container) {
synchronized (list) { 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); addition.set(amount);
wrapper = list.pick(); wrapper = list.pick();
} catch (RuntimeException ex) { } 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; package ru.dragonestia.picker.repository.impl.picker;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import ru.dragonestia.picker.api.exception.NoRoomsAvailableException; import ru.dragonestia.picker.exception.NoRoomsAvailableException;
import ru.dragonestia.picker.api.model.node.PickingMethod;
import ru.dragonestia.picker.model.entity.Entity; 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.InstanceContainer;
import ru.dragonestia.picker.repository.impl.container.RoomContainer; import ru.dragonestia.picker.repository.impl.container.RoomContainer;
@ -20,14 +20,14 @@ public class SequentialFillingPicker implements RoomPicker {
@Override @Override
public void add(RoomContainer container) { public void add(RoomContainer container) {
synchronized (wrappers) { synchronized (wrappers) {
wrappers.put(container.getRoom().getIdentifier(), new RoomWrapper(container)); wrappers.put(container.getRoom().getId().getValue(), new RoomWrapper(container));
} }
} }
@Override @Override
public void remove(RoomContainer container) { public void remove(RoomContainer container) {
synchronized (wrappers) { 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 @Override

View File

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

View File

@ -1,7 +1,7 @@
package ru.dragonestia.picker.service; package ru.dragonestia.picker.service;
import ru.dragonestia.picker.api.exception.RoomAreFullException; import ru.dragonestia.picker.exception.RoomAreFullException;
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.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.entity.Entity;
@ -10,15 +10,13 @@ import java.util.List;
public interface EntityService { 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); Collection<Entity> getRoomEntities(Room room);
List<ResponseUser> searchEntities(String input); List<Entity> searchEntities(EntityId input);
ResponseUser getEntityDetails(String userId);
} }

View File

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

View File

@ -1,11 +1,11 @@
package ru.dragonestia.picker.service; package ru.dragonestia.picker.service;
import ru.dragonestia.picker.api.exception.InvalidRoomIdentifierException; import ru.dragonestia.picker.exception.AlreadyExistsException;
import ru.dragonestia.picker.api.exception.RoomAlreadyExistException; import ru.dragonestia.picker.exception.InvalidIdentifierException;
import ru.dragonestia.picker.api.repository.response.PickedRoomResponse; import ru.dragonestia.picker.model.entity.EntityId;
import ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.InstanceId;
import ru.dragonestia.picker.model.room.Room; 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.Collection;
import java.util.Optional; import java.util.Optional;
@ -13,15 +13,15 @@ import java.util.Set;
public interface RoomService { public interface RoomService {
void create(Room room) throws InvalidRoomIdentifierException, RoomAlreadyExistException; void create(Room room) throws InvalidIdentifierException, AlreadyExistsException;
void remove(Room room); 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); void updateState(Room room);
} }

View File

@ -2,13 +2,13 @@ package ru.dragonestia.picker.service.impl;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.dragonestia.picker.api.exception.ConstantAdminParamsException;
import ru.dragonestia.picker.config.RoomPickerServerConfig; 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.Account;
import ru.dragonestia.picker.model.account.AccountId;
import ru.dragonestia.picker.model.account.Permission; import ru.dragonestia.picker.model.account.Permission;
import ru.dragonestia.picker.service.AccountService; import ru.dragonestia.picker.service.AccountService;
@ -30,39 +30,39 @@ public class AccountServiceImpl implements AccountService {
@PostConstruct @PostConstruct
void init() { 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())); 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) { public Account createNewAccount(AccountId id, String password) {
var account = new Account(username, passwordEncoder.encode(password)); var account = new Account(id, passwordEncoder.encode(password));
accounts.put(account.getUsername().toLowerCase(), account); accounts.put(account.getUsername().toLowerCase(), account);
return account; return account;
} }
@Override @Override
public @NotNull Optional<Account> findAccount(@NotNull String accountId) { public Optional<Account> findAccount(String accountId) {
return Optional.ofNullable(accounts.getOrDefault(accountId, null)); return Optional.ofNullable(accounts.getOrDefault(accountId, null));
} }
@Override @Override
public @NotNull Collection<Account> allAccounts() { public Collection<Account> allAccounts() {
return accounts.values().stream() return accounts.values().stream()
.filter(account -> !adminCredentials.username().equals(account.getUsername())) .filter(account -> !adminCredentials.username().equals(account.getUsername()))
.toList(); .toList();
} }
@Override @Override
public void removeAccount(@NotNull Account account) { public void removeAccount(Account account) {
checkAdmin(account.getUsername()); checkAdmin(account.getUsername());
accounts.remove(account.getUsername()); accounts.remove(account.getUsername());
account.setEnabled(false); account.setEnabled(false);
} }
@Override @Override
public void updateState(@NotNull Account account) { public void updateState(Account account) {
checkAdmin(account.getUsername()); checkAdmin(account.getUsername());
// TODO: save data to local storage // TODO: save data to local storage
} }
@ -79,7 +79,7 @@ public class AccountServiceImpl implements AccountService {
private void checkAdmin(String accountId) { private void checkAdmin(String accountId) {
if (adminCredentials.username().equals(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 lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; 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.room.Room;
import ru.dragonestia.picker.model.entity.Entity; import ru.dragonestia.picker.model.entity.Entity;
import ru.dragonestia.picker.repository.EntityRepository; import ru.dragonestia.picker.repository.EntityRepository;
@ -17,17 +17,17 @@ public class EntityServiceImpl implements EntityService {
private final EntityRepository entityRepository; private final EntityRepository entityRepository;
@Override @Override
public Collection<Room> getEntityRooms(Entity entity) { public Collection<Room> getEntityRooms(EntityId id) {
return entityRepository.findAllLinkedEntityRooms(entity); return entityRepository.findAllLinkedEntityRooms(id);
} }
@Override @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); entityRepository.linkWithRoom(room, entities, force);
} }
@Override @Override
public void unlinkEntitiesFromRoom(Room room, Collection<Entity> entities) { public void unlinkEntitiesFromRoom(Room room, Collection<EntityId> entities) {
entityRepository.unlinkWithRoom(room, entities); entityRepository.unlinkWithRoom(room, entities);
} }
@ -37,12 +37,7 @@ public class EntityServiceImpl implements EntityService {
} }
@Override @Override
public List<ResponseUser> searchEntities(String input) { public List<Entity> searchEntities(EntityId input) {
return entityRepository.search(input).stream().map(Entity::toResponseObject).toList(); return entityRepository.search(input).stream().toList();
}
@Override
public ResponseUser getEntityDetails(String userId) {
throw new UnsupportedOperationException("Not implemented");
} }
} }

View File

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

View File

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

View File

@ -66,7 +66,7 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override @Override
public void saveInstance(Instance instance) { public void saveInstance(Instance instance) {
if (!instance.isPersist()) return; 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(); var writer = objectMapper.writer();
try { try {
@ -79,16 +79,16 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override @Override
public void removeInstance(Instance instance) { public void removeInstance(Instance instance) {
if (!instance.isPersist()) return; 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 @SneakyThrows
@Override @Override
public void saveRoom(Room room) { public void saveRoom(Room room) {
if (!room.isPersist()) return; 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(); var writer = objectMapper.writer();
try { try {
@ -101,8 +101,8 @@ public class FileStorageImpl implements InstanceAndRoomStorage {
@Override @Override
public void removeRoom(Room room) { public void removeRoom(Room room) {
if (!room.isPersist()) return; 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.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity; 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.InstanceRepository;
import ru.dragonestia.picker.repository.RoomRepository; import ru.dragonestia.picker.repository.RoomRepository;
import ru.dragonestia.picker.repository.EntityRepository; import ru.dragonestia.picker.repository.EntityRepository;

View File

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

View File

@ -44,7 +44,7 @@ public class RoundRobinTests {
var users = entityRepository.entitiesOf(room); var users = entityRepository.entitiesOf(room);
Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation 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 { public static class PickingArgumentProvider implements ArgumentsProvider {

View File

@ -48,8 +48,8 @@ public class SequentialFillingTests {
var users = entityRepository.entitiesOf(room); var users = entityRepository.entitiesOf(room);
Assertions.assertTrue(slots == -1 || slots >= users.size()); // check slots limitation 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); 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.getIdentifier()); Assertions.assertEquals(expectedRoomId, room.getId());
} }
public static class PickingArgumentProvider implements ArgumentsProvider { 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); var node = new Instance(NodeIdentifier.of("test"), PickingMethod.SEQUENTIAL_FILLING, false);
Assertions.assertDoesNotThrow(() -> instanceService.create(node)); 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)); Assertions.assertThrows(InstanceAlreadyExistException.class, () -> instanceService.create(node));
instanceService.remove(node); instanceService.remove(node);
Assertions.assertFalse(() -> instanceService.find(node.getIdentifier()).isPresent()); Assertions.assertFalse(() -> instanceService.find(node.getId()).isPresent());
} }
@WithMockUser(roles = {"NODE_MANAGEMENT"}) @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.api.repository.type.EntityIdentifier;
import ru.dragonestia.picker.model.instance.Instance; import ru.dragonestia.picker.model.instance.Instance;
import ru.dragonestia.picker.model.entity.Entity; 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.List;
import java.util.Set; import java.util.Set;
@ -51,12 +51,12 @@ public class RoomServiceTests {
var room = roomFactory.create(RoomIdentifier.of("test-room"), instance, IRoom.UNLIMITED_SLOTS, "", false); var room = roomFactory.create(RoomIdentifier.of("test-room"), instance, IRoom.UNLIMITED_SLOTS, "", false);
roomService.create(room); 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)); Assertions.assertThrows(RoomAlreadyExistException.class, () -> roomService.create(room));
roomService.remove(room); roomService.remove(room);
Assertions.assertFalse(roomService.find(instance, room.getIdentifier()).isPresent()); Assertions.assertFalse(roomService.find(instance, room.getId()).isPresent());
} }
@WithMockUser(roles = {"NODE_MANAGEMENT"}) @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"}) @WithMockUser(roles = {"NODE_MANAGEMENT"})
@ -127,6 +127,6 @@ public class RoomServiceTests {
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.create(room)); Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.create(room));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.remove(room)); Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.remove(room));
Assertions.assertThrows(InstanceNotFoundException.class, () -> roomService.find(node, "Bruh")); 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")))));
} }
} }