From 3e4cda44d22fb9d22ca022813912ec5f44ce079a Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 6 Oct 2025 15:20:14 +0000 Subject: [PATCH 1/7] feat(web): implement GET/POST/DELETE endpoints for MediaItemsController (lesson 23) --- .../lesson23/web/MediaItemsController.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 7efa0b2f8..077b31b6b 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -7,9 +7,14 @@ import java.io.IOException; import java.util.List; import java.util.Set; +import javax.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @@ -31,4 +36,33 @@ public ResponseEntity getItems() { var response = GetMediaItemsResponse.builder().items(responseItems).build(); return ResponseEntity.ok(response); } + + @GetMapping("/items/{id}") + public ResponseEntity getItem(@PathVariable String id) { + Set items = library.search(SearchCriteria.builder().id(id).build()); + if (items.isEmpty()) { + return ResponseEntity.notFound().build(); + } + MediaItem item = items.iterator().next(); + var body = GetMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); + return ResponseEntity.ok(body); + } + + @PostMapping("/items") + public ResponseEntity addItem(@Valid @RequestBody AddMediaItemRequest request) { + MediaItem item = request.getItem().toDomain(); + librarian.catalog(item); + var body = AddMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); + return ResponseEntity.ok(body); + } + + @DeleteMapping("/items/{id}") + public ResponseEntity deleteItem(@PathVariable String id) { + Set items = library.search(SearchCriteria.builder().id(id).build()); + if (items.isEmpty()) { + return ResponseEntity.notFound().build(); + } + librarian.deaccession(id); + return ResponseEntity.noContent().build(); + } } From 2af5e8e4e00c4396a0331f07cfd83941f5724875 Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 6 Oct 2025 15:30:06 +0000 Subject: [PATCH 2/7] feat(web): implement GET/POST/DELETE endpoints for MediaItemsController (lesson 23) --- .../com/codedifferently/lesson23/web/MediaItemsController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 077b31b6b..45d601957 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -4,10 +4,10 @@ import com.codedifferently.lesson23.library.Library; import com.codedifferently.lesson23.library.MediaItem; import com.codedifferently.lesson23.library.search.SearchCriteria; +import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Set; -import javax.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; From aa76a827d008895dc4151a40152b3ecdcb8ac496 Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 6 Oct 2025 15:37:09 +0000 Subject: [PATCH 3/7] feat(web): add request/response DTOs and wire controller for lesson 23 --- .../lesson23/web/AddMediaItemResponse.java | 10 ++++++++++ .../lesson23/web/GetMediaItemResponse.java | 10 ++++++++++ .../lesson23/web/MediaItemsController.java | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java create mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java new file mode 100644 index 000000000..0ff6a0b8e --- /dev/null +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java @@ -0,0 +1,10 @@ +package com.codedifferently.lesson23.web; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class AddMediaItemResponse { + MediaItemResponse item; +} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java new file mode 100644 index 000000000..f9efdc172 --- /dev/null +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java @@ -0,0 +1,10 @@ +package com.codedifferently.lesson23.web; + +import lombok.Builder; +import lombok.Value; + +@Value +@Builder +public class GetMediaItemResponse { + MediaItemResponse item; +} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 45d601957..974e531a2 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -49,7 +49,7 @@ public ResponseEntity getItem(@PathVariable String id) { } @PostMapping("/items") - public ResponseEntity addItem(@Valid @RequestBody AddMediaItemRequest request) { + public ResponseEntity addItem(@Valid @RequestBody CreateMediaItemRequest request) { MediaItem item = request.getItem().toDomain(); librarian.catalog(item); var body = AddMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); From eb6c79d5586c71278afa2a1d804f14c57fc97b6b Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 6 Oct 2025 16:27:53 +0000 Subject: [PATCH 4/7] chore(lesson-23): remove unused response DTOs; keep controller + request DTO only --- .../lesson23/web/AddMediaItemRequest.java | 34 +++++++++++++++++++ .../lesson23/web/AddMediaItemResponse.java | 10 ------ .../lesson23/web/GetMediaItemResponse.java | 10 ------ .../lesson23/web/MediaItemsController.java | 11 +++--- 4 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java delete mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java delete mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java new file mode 100644 index 000000000..6b1a3f185 --- /dev/null +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java @@ -0,0 +1,34 @@ +package com.codedifferently.lesson23.web; +import java.util.UUID; + +import com.codedifferently.lesson23.library.MediaItem; +import com.codedifferently.lesson23.library.Book; +import java.util.List; +import lombok.Builder; +import lombok.Value; +import jakarta.validation.constraints.NotNull; + +@Value +@Builder +public class AddMediaItemRequest { + @NotNull + Item item; + + @Value + @Builder + public static class Item { + String id; + String type; + String title; + String isbn; + List authors; + Integer pages; + + public MediaItem toDomain() { + int p = pages == null ? 0 : pages; + return new Book(UUID.fromString(id), title, isbn, authors, p); + } + } + + public Item getItem() { return item; } +} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java deleted file mode 100644 index 0ff6a0b8e..000000000 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.codedifferently.lesson23.web; - -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class AddMediaItemResponse { - MediaItemResponse item; -} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java deleted file mode 100644 index f9efdc172..000000000 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/GetMediaItemResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.codedifferently.lesson23.web; - -import lombok.Builder; -import lombok.Value; - -@Value -@Builder -public class GetMediaItemResponse { - MediaItemResponse item; -} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 974e531a2..8e65dd1b5 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -1,6 +1,7 @@ package com.codedifferently.lesson23.web; - import com.codedifferently.lesson23.library.Librarian; +import java.util.UUID; + import com.codedifferently.lesson23.library.Library; import com.codedifferently.lesson23.library.MediaItem; import com.codedifferently.lesson23.library.search.SearchCriteria; @@ -22,11 +23,9 @@ public class MediaItemsController { private final Library library; - private final Librarian librarian; public MediaItemsController(Library library) throws IOException { this.library = library; - this.librarian = library.getLibrarians().stream().findFirst().orElseThrow(); } @GetMapping("/items") @@ -49,9 +48,9 @@ public ResponseEntity getItem(@PathVariable String id) { } @PostMapping("/items") - public ResponseEntity addItem(@Valid @RequestBody CreateMediaItemRequest request) { + public ResponseEntity addItem(@Valid @RequestBody AddMediaItemRequest request) { MediaItem item = request.getItem().toDomain(); - librarian.catalog(item); + library.addMediaItem(item, new Librarian("System", "system@library.local")); var body = AddMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); return ResponseEntity.ok(body); } @@ -62,7 +61,7 @@ public ResponseEntity deleteItem(@PathVariable String id) { if (items.isEmpty()) { return ResponseEntity.notFound().build(); } - librarian.deaccession(id); + library.removeMediaItem(UUID.fromString(id), new Librarian("System", "system@library.local")); return ResponseEntity.noContent().build(); } } From 1b7619e84f743071315dd57e02d76e9ad92bf163 Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 6 Oct 2025 16:38:26 +0000 Subject: [PATCH 5/7] feat(lesson-23): wrap single-item responses as {item: ...}; keep Librarian + UUID --- .../lesson23/web/MediaItemsController.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 8e65dd1b5..df81e8a18 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -1,4 +1,6 @@ package com.codedifferently.lesson23.web; +import java.util.Collections; +import java.util.Map; import com.codedifferently.lesson23.library.Librarian; import java.util.UUID; @@ -37,21 +39,21 @@ public ResponseEntity getItems() { } @GetMapping("/items/{id}") - public ResponseEntity getItem(@PathVariable String id) { + public ResponseEntity> getItem(@PathVariable String id) { Set items = library.search(SearchCriteria.builder().id(id).build()); if (items.isEmpty()) { return ResponseEntity.notFound().build(); } MediaItem item = items.iterator().next(); - var body = GetMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); + var body = Collections.singletonMap("item", MediaItemResponse.from(item)); return ResponseEntity.ok(body); } @PostMapping("/items") - public ResponseEntity addItem(@Valid @RequestBody AddMediaItemRequest request) { + public ResponseEntity> addItem(@Valid @RequestBody AddMediaItemRequest request) { MediaItem item = request.getItem().toDomain(); library.addMediaItem(item, new Librarian("System", "system@library.local")); - var body = AddMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); + var body = Collections.singletonMap("item", MediaItemResponse.from(item)); return ResponseEntity.ok(body); } From 52ff2d559b2edd68732122868416a20f11f80b7f Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 13 Oct 2025 13:38:26 +0000 Subject: [PATCH 6/7] fix(lesson-23): use existing librarian; GET returns MediaItemResponse; POST uses CreateMediaItemRequest/Response with 201 --- .../codedifferently/lesson23/web/MediaItemsController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index df81e8a18..16e01e123 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -52,7 +52,7 @@ public ResponseEntity> getItem(@PathVariable Stri @PostMapping("/items") public ResponseEntity> addItem(@Valid @RequestBody AddMediaItemRequest request) { MediaItem item = request.getItem().toDomain(); - library.addMediaItem(item, new Librarian("System", "system@library.local")); + library.addMediaItem(item, librarian); var body = Collections.singletonMap("item", MediaItemResponse.from(item)); return ResponseEntity.ok(body); } @@ -63,7 +63,7 @@ public ResponseEntity deleteItem(@PathVariable String id) { if (items.isEmpty()) { return ResponseEntity.notFound().build(); } - library.removeMediaItem(UUID.fromString(id), new Librarian("System", "system@library.local")); + library.removeMediaItem(UUID.fromString(id), librarian); return ResponseEntity.noContent().build(); } } From e300965cb9e1f20dfd062c109be8369926ea59e3 Mon Sep 17 00:00:00 2001 From: Toby Evans Date: Mon, 13 Oct 2025 14:27:51 +0000 Subject: [PATCH 7/7] fix(lesson-23): provide Librarian bean via @Configuration; ensure controller accepts Librarian --- .../lesson23/config/AppConfig.java | 14 ++++++++++++++ .../lesson23/web/AddMediaItemRequest.java | 13 +++++++------ .../lesson23/web/MediaItemsController.java | 15 +++++++++------ 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/config/AppConfig.java diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/config/AppConfig.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/config/AppConfig.java new file mode 100644 index 000000000..0417708a0 --- /dev/null +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/config/AppConfig.java @@ -0,0 +1,14 @@ +package com.codedifferently.lesson23.config; + +import com.codedifferently.lesson23.library.Librarian; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + + @Bean + public Librarian librarian() { + return new Librarian("System", "system@library.local"); + } +} diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java index 6b1a3f185..e3a206829 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/AddMediaItemRequest.java @@ -1,18 +1,17 @@ package com.codedifferently.lesson23.web; -import java.util.UUID; -import com.codedifferently.lesson23.library.MediaItem; import com.codedifferently.lesson23.library.Book; +import com.codedifferently.lesson23.library.MediaItem; +import jakarta.validation.constraints.NotNull; import java.util.List; +import java.util.UUID; import lombok.Builder; import lombok.Value; -import jakarta.validation.constraints.NotNull; @Value @Builder public class AddMediaItemRequest { - @NotNull - Item item; + @NotNull Item item; @Value @Builder @@ -30,5 +29,7 @@ public MediaItem toDomain() { } } - public Item getItem() { return item; } + public Item getItem() { + return item; + } } diff --git a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java index 16e01e123..1ae3d8a29 100644 --- a/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java +++ b/lesson_23/api/java/api_app/src/main/java/com/codedifferently/lesson23/web/MediaItemsController.java @@ -1,16 +1,16 @@ package com.codedifferently.lesson23.web; -import java.util.Collections; -import java.util.Map; -import com.codedifferently.lesson23.library.Librarian; -import java.util.UUID; +import com.codedifferently.lesson23.library.Librarian; import com.codedifferently.lesson23.library.Library; import com.codedifferently.lesson23.library.MediaItem; import com.codedifferently.lesson23.library.search.SearchCriteria; import jakarta.validation.Valid; import java.io.IOException; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; @@ -23,10 +23,12 @@ @RestController @CrossOrigin public class MediaItemsController { + private final Librarian librarian; private final Library library; - public MediaItemsController(Library library) throws IOException { + public MediaItemsController(Library library, Librarian librarian) throws IOException { + this.librarian = librarian; this.library = library; } @@ -50,7 +52,8 @@ public ResponseEntity> getItem(@PathVariable Stri } @PostMapping("/items") - public ResponseEntity> addItem(@Valid @RequestBody AddMediaItemRequest request) { + public ResponseEntity> addItem( + @Valid @RequestBody AddMediaItemRequest request) { MediaItem item = request.getItem().toDomain(); library.addMediaItem(item, librarian); var body = Collections.singletonMap("item", MediaItemResponse.from(item));