diff --git a/src/main/java/net/onelitefeather/vulpes/backend/controller/item/ItemLoreController.java b/src/main/java/net/onelitefeather/vulpes/backend/controller/item/ItemLoreController.java index 62ac9f8..d00af9d 100644 --- a/src/main/java/net/onelitefeather/vulpes/backend/controller/item/ItemLoreController.java +++ b/src/main/java/net/onelitefeather/vulpes/backend/controller/item/ItemLoreController.java @@ -8,6 +8,7 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Delete; import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Patch; import io.micronaut.http.annotation.PathVariable; import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Produces; @@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.inject.Inject; import net.onelitefeather.vulpes.backend.domain.item.ItemLoreDTO; +import net.onelitefeather.vulpes.backend.domain.item.ItemLoreReorderDTO; import net.onelitefeather.vulpes.backend.domain.item.ItemLoreResponseDTO; import net.onelitefeather.vulpes.backend.service.ItemService; import net.onelitefeather.vulpes.backend.validation.ValidationGroup; @@ -134,6 +136,33 @@ public HttpResponse createLore(@PathVariable("itemId") UUID return HttpResponse.ok(createResult); } + @Operation( + summary = "Reorder a lore entry of an item", + operationId = "reorderLore", + description = "Moves the lore entry identified by entryId to a new position within the lore list of the item identified by itemId.", + tags = {"Item"} + ) + @ApiResponse( + responseCode = "204", + description = "Lore successfully reordered." + ) + @ApiResponse( + responseCode = "400", + description = "Item or lore entry for the given ID was not found.", + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema(implementation = ItemLoreResponseDTO.ItemLoreErrorDTO.class) + ) + ) + @Patch("/{itemId}/lore/reorder") + @Validated + public HttpResponse reorderLore(@PathVariable("itemId") UUID itemId, @Body ItemLoreReorderDTO reorder) { + ItemLoreResponseDTO reorderResult = itemService.reorderLoreById(itemId, reorder.entryId(), reorder.newIndex()); + if (reorderResult instanceof ItemLoreResponseDTO.ItemLoreErrorDTO) { + return HttpResponse.badRequest(reorderResult); + } + return HttpResponse.noContent(); + } @Operation( summary = "Delete lore of an item", diff --git a/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreDTO.java b/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreDTO.java index 13e7435..727ca32 100644 --- a/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreDTO.java +++ b/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreDTO.java @@ -6,7 +6,6 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Null; -import jakarta.validation.constraints.Positive; import net.onelitefeather.vulpes.api.model.item.ItemLoreEntity; import java.util.UUID; @@ -23,18 +22,13 @@ public record ItemLoreDTO( UUID id, @Schema(description = "Text of the lore", requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank(groups = {Create.class, Update.class}) - String text, - @Schema(description = "Order index of the lore", requiredMode = Schema.RequiredMode.REQUIRED) - @Positive - int orderIndex + String text ) { - public ItemLoreEntity toEntity() { ItemLoreEntity entity = new ItemLoreEntity(); entity.setId(this.id); entity.setText(this.text); - entity.setOrderIndex(this.orderIndex); return entity; } } diff --git a/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreReorderDTO.java b/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreReorderDTO.java new file mode 100644 index 0000000..ca30863 --- /dev/null +++ b/src/main/java/net/onelitefeather/vulpes/backend/domain/item/ItemLoreReorderDTO.java @@ -0,0 +1,22 @@ +package net.onelitefeather.vulpes.backend.domain.item; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.serde.annotation.Serdeable; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; + +import java.util.UUID; + +@Schema(description = "Request DTO to reorder a lore entry of an item") +@Introspected +@Serdeable +public record ItemLoreReorderDTO( + @Schema(description = "ID of the lore entry to move", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull + UUID entryId, + @Schema(description = "Target index of the lore entry within the lore list", requiredMode = Schema.RequiredMode.REQUIRED) + @PositiveOrZero + int newIndex +) { +} diff --git a/src/main/java/net/onelitefeather/vulpes/backend/service/ItemService.java b/src/main/java/net/onelitefeather/vulpes/backend/service/ItemService.java index ca27a3e..c4ebc7d 100644 --- a/src/main/java/net/onelitefeather/vulpes/backend/service/ItemService.java +++ b/src/main/java/net/onelitefeather/vulpes/backend/service/ItemService.java @@ -144,6 +144,16 @@ public interface ItemService extends CrudService findLoreById(UUID id, Pageable pageable) { } @Override + @Transactional public ItemLoreResponseDTO updateLoreById(UUID id, ItemLoreDTO lore) { var byId = this.repository.findById(id); if (byId.isEmpty()) { return new ItemLoreResponseDTO.ItemLoreErrorDTO(GENERIC_ERROR); } var item = byId.get(); - var entity = lore.toEntity(); - entity.setItem(item); + var existingLore = this.itemLoreRepository.findById(lore.id()); + if (existingLore.isEmpty() || !existingLore.get().getItem().getId().equals(item.getId())) { + return new ItemLoreResponseDTO.ItemLoreErrorDTO(GENERIC_ERROR); + } + var entity = existingLore.get(); + entity.setText(lore.text()); var saved = this.itemLoreRepository.update(entity); return ItemLoreResponseDTO.ItemLoreDTO.createDTO(saved); } @Override + @Transactional public ItemLoreResponseDTO createLoreById(UUID id, ItemLoreDTO loreDto) { var byId = this.repository.findById(id); if (byId.isEmpty()) { @@ -137,6 +146,8 @@ public ItemLoreResponseDTO createLoreById(UUID id, ItemLoreDTO loreDto) { var item = byId.get(); var entity = loreDto.toEntity(); entity.setItem(item); + int nextIndex = (int) this.itemLoreRepository.findLoreById(item.getId(), Pageable.unpaged()).getTotalSize(); + entity.setOrderIndex(nextIndex); var saved = this.itemLoreRepository.save(entity); return ItemLoreResponseDTO.ItemLoreDTO.createDTO(saved); } @@ -160,6 +171,38 @@ public ItemLoreResponseDTO deleteLoreById(UUID id, UUID loreId) { return ItemLoreResponseDTO.ItemLoreDTO.createDTO(resolvedEntity); } + @Override + @Transactional + public ItemLoreResponseDTO reorderLoreById(UUID id, UUID entryId, int newIndex) { + var byId = this.repository.findById(id); + if (byId.isEmpty()) { + return new ItemLoreResponseDTO.ItemLoreErrorDTO(GENERIC_ERROR); + } + var item = byId.get(); + List loreLines = new ArrayList<>( + this.itemLoreRepository.findLoreById(item.getId(), Pageable.unpaged()).getContent()); + + var entryToMove = loreLines.stream() + .filter(entry -> entry.getId().equals(entryId)) + .findFirst() + .orElse(null); + + if (entryToMove == null) { + return new ItemLoreResponseDTO.ItemLoreErrorDTO(GENERIC_ERROR); + } + + loreLines.remove(entryToMove); + int clampedIndex = Math.max(0, Math.min(newIndex, loreLines.size())); + loreLines.add(clampedIndex, entryToMove); + + for (int i = 0; i < loreLines.size(); i++) { + loreLines.get(i).setOrderIndex(i); + } + this.itemLoreRepository.updateAll(loreLines); + + return ItemLoreResponseDTO.ItemLoreDTO.createDTO(entryToMove); + } + @Override public List deleteAllLoreById(UUID id) { var byId = this.repository.findById(id);