From 515bb848b11e63104c6a0051f87a3cb71c78f638 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Wed, 12 Mar 2025 18:42:20 +0100 Subject: [PATCH 1/4] feat: add /undo-patch endpoint --- .../patchroulette/controller/RESTController.java | 11 +++++++++++ .../papermc/patchroulette/service/PatchService.java | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/java/io/papermc/patchroulette/controller/RESTController.java b/src/main/java/io/papermc/patchroulette/controller/RESTController.java index 781ec38..aaad8d3 100644 --- a/src/main/java/io/papermc/patchroulette/controller/RESTController.java +++ b/src/main/java/io/papermc/patchroulette/controller/RESTController.java @@ -115,4 +115,15 @@ public ResponseEntity cancelPatch(@RequestBody final PatchId input) { return ResponseEntity.ok("Patch cancelled."); } + @PreAuthorize("hasRole('PATCH')") + @PostMapping( + value = "/undo-patch", + consumes = "application/json", + produces = "text/plain" + ) + public ResponseEntity undoPatch(final Authentication auth, @RequestBody final PatchId input) { + final String user = this.getUser(auth); + this.patchService.undoPatch(input, user); + return ResponseEntity.ok("Patch moved to WIP."); + } } diff --git a/src/main/java/io/papermc/patchroulette/service/PatchService.java b/src/main/java/io/papermc/patchroulette/service/PatchService.java index 3a22be5..6a4299d 100644 --- a/src/main/java/io/papermc/patchroulette/service/PatchService.java +++ b/src/main/java/io/papermc/patchroulette/service/PatchService.java @@ -78,6 +78,17 @@ public void finishWorkOnPatch(final PatchId patchId, final String user) { this.patchRepository.save(patch); } + @Transactional + public void undoPatch(final PatchId patchId, final String user) { + final Patch patch = this.patchRepository.getReferenceById(patchId); + if (patch.getStatus() != Status.DONE) { + throw new IllegalStateException("Patch " + patchId + " is not DONE"); + } + patch.setStatus(Status.WIP); + patch.setResponsibleUser(user); + this.patchRepository.save(patch); + } + public void clearPatches(final String minecraftVersion) { this.patchRepository.deleteAllByMinecraftVersion(minecraftVersion); } From a53e097ddf091704c96e13753c516a5eafe21322 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Wed, 12 Mar 2025 18:47:53 +0100 Subject: [PATCH 2/4] feat: handle errors better --- .../papermc/patchroulette/controller/RESTController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/papermc/patchroulette/controller/RESTController.java b/src/main/java/io/papermc/patchroulette/controller/RESTController.java index aaad8d3..3cd0983 100644 --- a/src/main/java/io/papermc/patchroulette/controller/RESTController.java +++ b/src/main/java/io/papermc/patchroulette/controller/RESTController.java @@ -5,9 +5,11 @@ import io.papermc.patchroulette.service.PatchService; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -126,4 +128,9 @@ public ResponseEntity undoPatch(final Authentication auth, @RequestBody this.patchService.undoPatch(input, user); return ResponseEntity.ok("Patch moved to WIP."); } + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleException(final IllegalStateException e) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(e.getMessage()); + } } From e87063065901481a0b16cfa27a01ce3a92cdb35c Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Wed, 12 Mar 2025 19:00:17 +0100 Subject: [PATCH 3/4] feat: allow bulk patch starts --- .../controller/RESTController.java | 20 ++++++++++++- .../papermc/patchroulette/model/PatchId.java | 8 ++++++ .../patchroulette/service/PatchService.java | 28 ++++++++++++------- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/papermc/patchroulette/controller/RESTController.java b/src/main/java/io/papermc/patchroulette/controller/RESTController.java index 3cd0983..0d61582 100644 --- a/src/main/java/io/papermc/patchroulette/controller/RESTController.java +++ b/src/main/java/io/papermc/patchroulette/controller/RESTController.java @@ -90,10 +90,28 @@ private String getUser(final Authentication authentication) { ) public ResponseEntity startPatch(final Authentication auth, @RequestBody final PatchId input) { final String user = this.getUser(auth); - this.patchService.startWorkOnPatch(input, user); + final List result = this.patchService.startWorkOnPatches(input.getMinecraftVersion(), List.of(input.getPath()), user); + if (result.isEmpty()) { + return ResponseEntity.status(HttpStatus.CONFLICT).body("Patch not available."); + } return ResponseEntity.ok("Patch started."); } + @PreAuthorize("hasRole('PATCH')") + @PostMapping( + value = "/start-patches", + consumes = "application/json", + produces = "application/json" + ) + public ResponseEntity startPatch(final Authentication auth, @RequestBody final Patches input) { + final String user = this.getUser(auth); + final List result = this.patchService.startWorkOnPatches(input.minecraftVersion(), input.paths(), user); + if (result.isEmpty()) { + return ResponseEntity.status(HttpStatus.CONFLICT).body("None of the patches are available."); + } + return ResponseEntity.ok(result); + } + @PreAuthorize("hasRole('PATCH')") @PostMapping( value = "/complete-patch", diff --git a/src/main/java/io/papermc/patchroulette/model/PatchId.java b/src/main/java/io/papermc/patchroulette/model/PatchId.java index f0c02dd..1f648e9 100644 --- a/src/main/java/io/papermc/patchroulette/model/PatchId.java +++ b/src/main/java/io/papermc/patchroulette/model/PatchId.java @@ -13,6 +13,14 @@ public class PatchId implements Serializable { public PatchId() { } + public String getMinecraftVersion() { + return minecraftVersion; + } + + public String getPath() { + return path; + } + @JsonCreator public PatchId(@JsonProperty("minecraftVersion") final String minecraftVersion, @JsonProperty("path") final String path) { diff --git a/src/main/java/io/papermc/patchroulette/service/PatchService.java b/src/main/java/io/papermc/patchroulette/service/PatchService.java index 6a4299d..72f6692 100644 --- a/src/main/java/io/papermc/patchroulette/service/PatchService.java +++ b/src/main/java/io/papermc/patchroulette/service/PatchService.java @@ -4,6 +4,8 @@ import io.papermc.patchroulette.model.PatchId; import io.papermc.patchroulette.model.Status; import io.papermc.patchroulette.repository.PatchRepository; + +import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,17 +43,23 @@ public List getAllPatches(final String minecraftVersion) { } @Transactional - public void startWorkOnPatch(final PatchId patchId, final String user) { - final Patch patch = this.patchRepository.getReferenceById(patchId); - if (patch.getStatus() != Status.AVAILABLE) { - throw new IllegalStateException("Patch " + patchId + " is not available"); - } - if (patch.getResponsibleUser() != null) { - throw new IllegalStateException("Patch " + patchId + " is already claimed by another user"); + public List startWorkOnPatches(final String minecraftVersion, final List patches, final String user) { + final List startedPatches = new ArrayList<>(); + for (final String path : patches) { + final PatchId patchId = new PatchId(minecraftVersion, path); + final Patch patch = this.patchRepository.getReferenceById(patchId); + if (patch.getStatus() != Status.AVAILABLE) { + continue; + } + if (patch.getResponsibleUser() != null) { + continue; + } + patch.setStatus(Status.WIP); + patch.setResponsibleUser(user); + this.patchRepository.save(patch); + startedPatches.add(path); } - patch.setStatus(Status.WIP); - patch.setResponsibleUser(user); - this.patchRepository.save(patch); + return startedPatches; } @Transactional From 42f715986c6f2db7934ee60fff039bd3de5ce347 Mon Sep 17 00:00:00 2001 From: MiniDigger | Martin Date: Wed, 12 Mar 2025 19:39:10 +0100 Subject: [PATCH 4/4] feat: track patch sizes (implements #1) --- .../controller/RESTController.java | 21 +++++++++++-------- .../io/papermc/patchroulette/model/Patch.java | 9 ++++++++ .../patchroulette/service/PatchService.java | 9 +++++--- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/papermc/patchroulette/controller/RESTController.java b/src/main/java/io/papermc/patchroulette/controller/RESTController.java index 0d61582..7304eb9 100644 --- a/src/main/java/io/papermc/patchroulette/controller/RESTController.java +++ b/src/main/java/io/papermc/patchroulette/controller/RESTController.java @@ -1,6 +1,5 @@ package io.papermc.patchroulette.controller; -import io.papermc.patchroulette.model.Patch; import io.papermc.patchroulette.model.PatchId; import io.papermc.patchroulette.service.PatchService; import java.util.List; @@ -26,20 +25,22 @@ public RESTController(final PatchService patchService) { this.patchService = patchService; } + public record PatchInfo(String path, Integer size) {} + @PreAuthorize("hasRole('PATCH')") @GetMapping( value = "/get-available-patches", produces = "application/json" ) - public ResponseEntity> getAvailablePatches(@RequestParam final String minecraftVersion) { + public ResponseEntity> getAvailablePatches(@RequestParam final String minecraftVersion) { return ResponseEntity.ok( this.patchService.getAvailablePatches(minecraftVersion).stream() - .map(Patch::getPath) + .map(patch -> new PatchInfo(patch.getPath(), patch.getSize())) .toList() ); } - public record PatchDetails(String path, String status, String responsibleUser) {} + public record PatchDetails(String path, String status, String responsibleUser, Integer size) {} @PreAuthorize("hasRole('PATCH')") @GetMapping( @@ -49,12 +50,12 @@ public record PatchDetails(String path, String status, String responsibleUser) { public ResponseEntity> getAllPatches(@RequestParam final String minecraftVersion) { return ResponseEntity.ok( this.patchService.getAllPatches(minecraftVersion).stream() - .map(patch -> new PatchDetails(patch.getPath(), patch.getStatus().name(), patch.getResponsibleUser())) + .map(patch -> new PatchDetails(patch.getPath(), patch.getStatus().name(), patch.getResponsibleUser(), patch.getSize())) .toList() ); } - public record Patches(String minecraftVersion, List paths) {} + public record Patches(String minecraftVersion, List patches) {} @PreAuthorize("hasRole('PATCH')") @PostMapping( @@ -63,7 +64,7 @@ public record Patches(String minecraftVersion, List paths) {} produces = "text/plain" ) public ResponseEntity setPatches(@RequestBody final Patches input) { - this.patchService.setPatches(input.minecraftVersion(), input.paths()); + this.patchService.setPatches(input.minecraftVersion(), input.patches()); return ResponseEntity.ok("Patches set."); } @@ -97,15 +98,17 @@ public ResponseEntity startPatch(final Authentication auth, @RequestBody return ResponseEntity.ok("Patch started."); } + public record PatchList(String minecraftVersion, List patches) {} + @PreAuthorize("hasRole('PATCH')") @PostMapping( value = "/start-patches", consumes = "application/json", produces = "application/json" ) - public ResponseEntity startPatch(final Authentication auth, @RequestBody final Patches input) { + public ResponseEntity startPatch(final Authentication auth, @RequestBody final PatchList input) { final String user = this.getUser(auth); - final List result = this.patchService.startWorkOnPatches(input.minecraftVersion(), input.paths(), user); + final List result = this.patchService.startWorkOnPatches(input.minecraftVersion(), input.patches(), user); if (result.isEmpty()) { return ResponseEntity.status(HttpStatus.CONFLICT).body("None of the patches are available."); } diff --git a/src/main/java/io/papermc/patchroulette/model/Patch.java b/src/main/java/io/papermc/patchroulette/model/Patch.java index 73c1925..451ac47 100644 --- a/src/main/java/io/papermc/patchroulette/model/Patch.java +++ b/src/main/java/io/papermc/patchroulette/model/Patch.java @@ -22,6 +22,7 @@ public class Patch { private Status status; private String responsibleUser; + private Integer size; public Patch() { } @@ -57,4 +58,12 @@ public String getResponsibleUser() { public void setResponsibleUser(final String responsibleUser) { this.responsibleUser = responsibleUser; } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } } diff --git a/src/main/java/io/papermc/patchroulette/service/PatchService.java b/src/main/java/io/papermc/patchroulette/service/PatchService.java index 72f6692..6d1a9b4 100644 --- a/src/main/java/io/papermc/patchroulette/service/PatchService.java +++ b/src/main/java/io/papermc/patchroulette/service/PatchService.java @@ -11,6 +11,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import static io.papermc.patchroulette.controller.RESTController.PatchInfo; + @Service public class PatchService { @@ -22,10 +24,11 @@ public PatchService(final PatchRepository patchRepository) { } @Transactional - public void setPatches(final String minecraftVersion, final List paths) { - final List patches = paths.stream().map(path -> { + public void setPatches(final String minecraftVersion, final List patchInfos) { + final List patches = patchInfos.stream().map(patchInfo -> { final Patch patch = new Patch(); - patch.setPath(path); + patch.setPath(patchInfo.path()); + patch.setSize(patchInfo.size()); patch.setStatus(Status.AVAILABLE); patch.setMinecraftVersion(minecraftVersion); return patch;