diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java index d4bfb7bf..4acf3659 100644 --- a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/MediaItemsController.java @@ -3,14 +3,27 @@ import com.codedifferently.lesson16.library.Librarian; import com.codedifferently.lesson16.library.Library; import com.codedifferently.lesson16.library.MediaItem; +import com.codedifferently.lesson16.library.exceptions.MediaItemCheckedOutException; import com.codedifferently.lesson16.library.search.SearchCriteria; +import jakarta.validation.Valid; import java.io.IOException; import java.util.List; import java.util.Set; +import java.util.UUID; +import org.springframework.http.HttpStatus; +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; +import org.springframework.web.server.ResponseStatusException; +/** + * @author vscode + */ @RestController @CrossOrigin public class MediaItemsController { @@ -29,4 +42,37 @@ public GetMediaItemsResponse getItems() { var response = GetMediaItemsResponse.builder().items(responseItems).build(); return response; } + + @PostMapping("/items") + public CreateMediaItemResponse postItem(@Valid @RequestBody CreateMediaItemRequest request) { + MediaItem item = MediaItemRequest.asMediaItem(request.getItem()); + library.addMediaItem(item, librarian); + var response = CreateMediaItemResponse.builder().item(MediaItemResponse.from(item)).build(); + return response; + } + + @GetMapping("/items/{id}") + public GetMediaItemsResponse getMediaItem(@PathVariable String id) { + Set items = library.search(SearchCriteria.builder().id(id).build()); + if (items.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Media item not found"); + } + List responseItems = items.stream().map(MediaItemResponse::from).toList(); + var response = GetMediaItemsResponse.builder().items(responseItems).build(); + return response; + } + + @DeleteMapping("/items/{id}") + public ResponseEntity deleteItem(@PathVariable("id") UUID id) { + if (!library.hasMediaItem(id)) { + return ResponseEntity.notFound().build(); + } + try { + library.removeMediaItem(id, new Librarian("Default", "librarian@example.com")); + return ResponseEntity.noContent().build(); + } catch (MediaItemCheckedOutException e) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, "Cannot delete checked out item", e); + } + } } diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java new file mode 100644 index 00000000..5f424143 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsController.java @@ -0,0 +1,67 @@ +package com.codedifferently.lesson16.web; + +import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.LibraryGuest; +import com.codedifferently.lesson16.library.exceptions.MediaItemCheckedOutException; +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/patrons") +public class PatronsController { + + private final Library library; + private final Map patronMap; + + public PatronsController(Library library) { + this.library = library; + this.patronMap = new HashMap<>(); + } + + @GetMapping + public ResponseEntity> getPatrons() { + Collection patrons = library.getPatrons(); + return ResponseEntity.ok(patrons); + } + + @PostMapping + public ResponseEntity createPatron(@Valid @RequestBody PatronsRequest request) { + LibraryGuest guest = PatronsRequest.asLibraryGuest(request); + library.addLibraryGuest(guest); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping("/{id}") + public ResponseEntity getPatronById(@PathVariable UUID id) { + LibraryGuest patron = patronMap.get(id); + if (patron != null) { + return ResponseEntity.ok(patron); + } else { + return ResponseEntity.notFound().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity removePatron(@PathVariable UUID id) { + try { + library.removeLibraryGuest(id); + return ResponseEntity.noContent().build(); + } catch (MediaItemCheckedOutException e) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } catch (Exception e) { + return ResponseEntity.notFound().build(); + } + } +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsRequest.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsRequest.java new file mode 100644 index 00000000..f82e2d39 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsRequest.java @@ -0,0 +1,32 @@ +package com.codedifferently.lesson16.web; + +import com.codedifferently.lesson16.library.LibraryGuest; +import com.codedifferently.lesson16.library.Patron; +import jakarta.validation.constraints.NotBlank; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class PatronsRequest { + private UUID id; + + @NotBlank(message = "Name is required") + private String name; + + @NotBlank(message = "Email is required") + private String email; + + public static LibraryGuest asLibraryGuest(PatronsRequest request) { + UUID id = request.getId(); + String name = request.getName(); + String email = request.getEmail(); + + return new Patron(name, email); + } +} diff --git a/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsResponse.java b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsResponse.java new file mode 100644 index 00000000..cf7e3258 --- /dev/null +++ b/lesson_16/api/api_app/src/main/java/com/codedifferently/lesson16/web/PatronsResponse.java @@ -0,0 +1,24 @@ +package com.codedifferently.lesson16.web; + +import com.codedifferently.lesson16.library.LibraryGuest; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PatronsResponse { + private UUID id; + private String name; + + public static PatronsResponse from(LibraryGuest guest) { + return builder().id(guest.getId()).name(guest.getName()).build(); + } + + public static List from(Collection guests) { + return guests.stream().map(PatronsResponse::from).collect(Collectors.toList()); + } +} diff --git a/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java new file mode 100644 index 00000000..8f92734f --- /dev/null +++ b/lesson_16/api/api_app/src/test/java/com/codedifferently/lesson16/web/PatronsControllerTest.java @@ -0,0 +1,65 @@ +package com.codedifferently.lesson16.web; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.codedifferently.lesson16.library.Library; +import com.codedifferently.lesson16.library.LibraryGuest; +import java.util.Collections; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +class PatronsControllerTest { + + private MockMvc mockMvc; + private Library library; + + @BeforeEach + public void setUp() { + library = mock(Library.class); + mockMvc = MockMvcBuilders.standaloneSetup(new PatronsController(library)).build(); + } + + @Test + public void testGetPatrons() throws Exception { + // Mocking + Set patrons = Collections.emptySet(); + when(library.getPatrons()).thenReturn(patrons); + + // Execution and Assertion + mockMvc + .perform(get("/patrons")) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + } + + @Test + public void testCreatePatron() throws Exception { + // Mocking + mockMvc + .perform( + post("/patrons") + .contentType(MediaType.APPLICATION_JSON) + .content( + "{\"id\": \"123e4567-e89b-12d3-a456-426614174000\", \"name\": \"John Doe\", \"email\": \"john@example.com\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isCreated()); + } + + @Test + public void testRemovePatron() throws Exception { + // Execution and Assertion + mockMvc + .perform(delete("/patrons/{id}", "123e4567-e89b-12d3-a456-426614174000")) + .andExpect(status().isNoContent()); + } +}