From 23bf5c41b4222f9fa67ba1036a8b2260fae3991a Mon Sep 17 00:00:00 2001 From: lobo Date: Wed, 30 Sep 2026 15:00:47 +0200 Subject: [PATCH] chore(library)!: drop the full-replace PUT of a backlog entry The Frontend sends PATCH since item 27 batch 2. --- readme.md | 1 - .../library/api/LibraryController.java | 11 --- .../library/service/BacklogService.java | 17 ----- .../api/LibraryControllerBacklogTest.java | 73 +------------------ .../api/PublicProfileControllerTest.java | 4 +- 5 files changed, 6 insertions(+), 100 deletions(-) diff --git a/readme.md b/readme.md index 2e94b35..745825c 100644 --- a/readme.md +++ b/readme.md @@ -117,7 +117,6 @@ ArchUnit role rules (`RoleAnnotationRules`) and `src/test/resources/contract/aut |---|---|---| | `GET` | `/api/library/games`, `/api/library/games/{appid}` | JWT | | `PATCH` | `/api/library/games/{appid}/backlog` (only the sent fields; `null` clears one) | JWT | -| `PUT` | `/api/library/games/{appid}/backlog` (replaces all four; removed in item 29) | JWT | | `PUT`, `DELETE` | `/api/library/games/{appid}/corrections/{field}` (`{value}`; `name`, `image`, `speedrun`, `moods`, `endless`, `genres`; `204`, `400 INVALID_CORRECTION`) | JWT, AUDITOR | | `POST` | `/api/library/games/{appid}/merge` (`{otherAppid}`; `200 {appid}` = the key that stays, `400 SAME_GAME` / `STEAM_GAMES`, `404 GAME_NOT_FOUND`) | JWT, AUDITOR | | `PUT` | `/api/library/games/{appid}/status` (status only, keeps rating, notes, priority) | JWT | diff --git a/src/main/java/core_service/modules/library/api/LibraryController.java b/src/main/java/core_service/modules/library/api/LibraryController.java index aa1c07d..dd6c9f2 100644 --- a/src/main/java/core_service/modules/library/api/LibraryController.java +++ b/src/main/java/core_service/modules/library/api/LibraryController.java @@ -3,7 +3,6 @@ package core_service.modules.library.api; import core_service.shared.api.ErrorResponse; import core_service.modules.library.api.dto.AchievementSummaryDto; import core_service.modules.library.api.dto.ActivityDto; -import core_service.modules.library.api.dto.BacklogEntryDto; import core_service.modules.library.api.dto.BacklogPatch; import core_service.modules.library.api.dto.BrokenImageReport; import core_service.modules.library.api.dto.CorrectionDto; @@ -219,16 +218,6 @@ public class LibraryController { return ResponseEntity.badRequest().body(new ErrorResponse("INVALID_CORRECTION", message)); } - @PutMapping("/games/{appid}/backlog") - public ResponseEntity updateBacklog(@AuthenticationPrincipal UserPrincipal principal, - @PathVariable("appid") Long appid, - @Valid @RequestBody BacklogEntryDto entry) { - return backlogService.update(principal.getIdAsLong(), appid, entry) - .>map(ResponseEntity::ok) - .orElseGet(() -> ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(GAME_NOT_FOUND)); - } - @PatchMapping("/games/{appid}/backlog") public ResponseEntity patchBacklog(@AuthenticationPrincipal UserPrincipal principal, @PathVariable("appid") Long appid, diff --git a/src/main/java/core_service/modules/library/service/BacklogService.java b/src/main/java/core_service/modules/library/service/BacklogService.java index 905850b..e890042 100644 --- a/src/main/java/core_service/modules/library/service/BacklogService.java +++ b/src/main/java/core_service/modules/library/service/BacklogService.java @@ -24,23 +24,6 @@ public class BacklogService { private final UserGameRepository userGameRepository; - /** - * @return empty when the game is not in the user's active library - */ - @Transactional - public Optional update(Long userId, Long appid, BacklogEntryDto entry) { - return userGameRepository.findById(new UserGameId(userId, appid)) - .filter(game -> game.getRemovedAt() == null) - .map(game -> { - game.setStatus(entry.status()); - game.setStatusSetByUser(true); - game.setRating(entry.rating()); - game.setNotes(entry.notes() == null || entry.notes().isBlank() ? null : entry.notes()); - game.setPriority(entry.priority()); - return toDto(game); - }); - } - /** * Changes only the fields the patch sends. Only a sent status counts as set by the user, so a save of the notes * alone still lets the sync move the game from BACKLOG to PLAYING. diff --git a/src/test/java/core_service/modules/library/api/LibraryControllerBacklogTest.java b/src/test/java/core_service/modules/library/api/LibraryControllerBacklogTest.java index bf246e8..f0bd045 100644 --- a/src/test/java/core_service/modules/library/api/LibraryControllerBacklogTest.java +++ b/src/test/java/core_service/modules/library/api/LibraryControllerBacklogTest.java @@ -60,88 +60,23 @@ class LibraryControllerBacklogTest extends AbstractIntegrationTest { } @Test - void updateBacklog_StoresAllFields() throws Exception { - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"FINISHED\",\"rating\":4,\"notes\":\"Great ending\",\"priority\":\"HIGH\"}")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.status").value("FINISHED")) - .andExpect(jsonPath("$.rating").value(4)); - - UserGame game = stored(); - assertEquals(GameStatus.FINISHED, game.getStatus()); - assertEquals(4, game.getRating()); - assertEquals("Great ending", game.getNotes()); - assertEquals("HIGH", game.getPriority().name()); - } - - @Test - void updateBacklog_NullsClearOptionalFieldsAndBlankNotesAreNull() throws Exception { - UserGame game = stored(); - game.setRating(5); - game.setNotes("old"); - userGameRepository.save(game); - - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"DROPPED\",\"notes\":\" \"}")) - .andExpect(status().isOk()); - - game = stored(); - assertEquals(GameStatus.DROPPED, game.getStatus()); - assertNull(game.getRating()); - assertNull(game.getNotes()); - assertNull(game.getPriority()); - } - - @ParameterizedTest - @ValueSource(strings = { - "{}", - "{\"status\":\"WISHLIST\"}", - "{\"status\":\"PLAYING\",\"rating\":0}", - "{\"status\":\"PLAYING\",\"rating\":6}", - "{\"status\":\"PLAYING\",\"priority\":\"URGENT\"}"}) - void updateBacklog_InvalidBody_Is400AndStoresNothing(String body) throws Exception { - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON).content(body)) - .andExpect(status().isBadRequest()); - - assertEquals(GameStatus.BACKLOG, stored().getStatus()); - } - - @Test - void updateBacklog_FractionalRating_Is400() throws Exception { - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"PLAYING\",\"rating\":4.7}")) - .andExpect(status().isBadRequest()); + void patchBacklog_FractionalRating_Is400() throws Exception { + patchBacklog(APPID, "{\"rating\":4.7}").andExpect(status().isBadRequest()); assertNull(stored().getRating()); } @Test - void updateBacklog_NotesTooLong_Is400() throws Exception { - String notes = "x".repeat(2001); - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"PLAYING\",\"notes\":\"" + notes + "\"}")) - .andExpect(status().isBadRequest()); - } - - @Test - void updateBacklog_RemovedGame_Is404() throws Exception { + void patchBacklog_RemovedGame_Is404() throws Exception { UserGame game = stored(); game.setRemovedAt(Instant.now()); userGameRepository.save(game); - mockMvc.perform(put("/api/library/games/220/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"PLAYING\"}")) + patchBacklog(APPID, "{\"notes\":\"x\"}") .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value("GAME_NOT_FOUND")); } - @Test - void updateBacklog_GameNotOwned_Is404() throws Exception { - mockMvc.perform(put("/api/library/games/999/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test").contentType(MediaType.APPLICATION_JSON) - .content("{\"status\":\"PLAYING\"}")) - .andExpect(status().isNotFound()); - } - private ResultActions patchBacklog(long appid, String body) throws Exception { return mockMvc.perform(patch("/api/library/games/" + appid + "/backlog").header(ClientHeaderFilter.CLIENT_HEADER, "test") .contentType(MediaType.APPLICATION_JSON).content(body)); diff --git a/src/test/java/core_service/modules/library/api/PublicProfileControllerTest.java b/src/test/java/core_service/modules/library/api/PublicProfileControllerTest.java index e0b91e2..aaf5bb8 100644 --- a/src/test/java/core_service/modules/library/api/PublicProfileControllerTest.java +++ b/src/test/java/core_service/modules/library/api/PublicProfileControllerTest.java @@ -24,7 +24,7 @@ import java.util.List; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.nullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -189,7 +189,7 @@ class PublicProfileControllerTest extends AbstractIntegrationTest { // The dev filter falls back to the dummy user 1 for a token it rejects; user 7's game would answer 200. @Test void profileTicket_IsNoLoginForTheLibraryApi() throws Exception { - mockMvc.perform(put("/api/library/games/220/backlog").header("Authorization", "Bearer " + ticket(USER_ID)) + mockMvc.perform(patch("/api/library/games/220/backlog").header("Authorization", "Bearer " + ticket(USER_ID)) .contentType("application/json").content("{\"status\":\"DROPPED\"}")) .andExpect(status().isNotFound()); }