Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Added functions postItems, getMediaItem, and deleteMediaItem #362

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.codedifferently.lesson16.web;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CreatePatronRequest {
@NotNull(message = "patron is required") @Valid
private PatronRequest patron;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.codedifferently.lesson16.web;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class CreatePatronResponse {
private PatronResponse patron;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.codedifferently.lesson16.web;

import java.util.List;
import lombok.Builder;
import lombok.Data;
import lombok.Singular;

@Data
@Builder
public class GetPatronsResponse {
@Singular private List<PatronResponse> patrons;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@
import com.codedifferently.lesson16.library.Library;
import com.codedifferently.lesson16.library.MediaItem;
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;

// ___________________________________________________________
// THIS CODE WAS MADE IN COLLABORATION WITH VICENTE AND RICH
// ___________________________________________________________

@RestController
@CrossOrigin
Expand All @@ -29,4 +42,37 @@ public GetMediaItemsResponse getItems() {
var response = GetMediaItemsResponse.builder().items(responseItems).build();
return response;
}

/**
* Post an item to the specified endpoint.
*
* @param req the request object for creating a media item
* @return the response object for creating a media item
*/
@PostMapping("/items")
public CreateMediaItemResponse postItem(@Valid @RequestBody CreateMediaItemRequest req) {
MediaItem media = MediaItemRequest.asMediaItem(req.getItem());
library.addMediaItem(media, librarian);
return CreateMediaItemResponse.builder().item(MediaItemResponse.from(media)).build();
}

@GetMapping("/items/{id}")
public GetMediaItemsResponse getItem(@PathVariable UUID id) {
Set<MediaItem> items = library.search(SearchCriteria.builder().id(id.toString()).build());
if (items.isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Media item not found");
}
List<MediaItemResponse> responseItems = items.stream().map(MediaItemResponse::from).toList();
var response = GetMediaItemsResponse.builder().items(responseItems).build();
return response;
}

@DeleteMapping("/items/{id}")
public ResponseEntity<Void> deleteItem(@PathVariable UUID id) {
if (!library.hasMediaItem(id)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Media item not found");
Moibrahi7 marked this conversation as resolved.
Show resolved Hide resolved
}
library.removeMediaItem(id, librarian);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.codedifferently.lesson16.web;

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 PatronRequest {
private UUID id;

@NotBlank(message = "Email is required")
private String email;

@NotBlank(message = "Name is required")
private String name;

public static Patron asPatron(PatronRequest request) {
return new Patron(request.name, request.email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.codedifferently.lesson16.web;

import com.codedifferently.lesson16.library.LibraryGuest;
import jakarta.validation.constraints.NotBlank;
import java.util.UUID;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class PatronResponse {
private UUID id;

@NotBlank(message = "Email is required")
private String email;

@NotBlank(message = "Name is required")
private String name;

public static PatronResponse from(LibraryGuest patron) {
var result =
PatronResponse.builder().id(patron.getId()).name(patron.getName()).email(patron.getEmail());
return result.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.codedifferently.lesson16.web;

import com.codedifferently.lesson16.library.Library;
import com.codedifferently.lesson16.library.LibraryGuest;
import com.codedifferently.lesson16.library.Patron;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.HashSet;
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.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;

// ___________________________________________________________
// THIS CODE WAS MADE IN COLLABORATION WITH VICENTE AND RICH
// ___________________________________________________________

@RestController
public class PatronsController {
private final Library library;

public PatronsController(Library library) throws IOException {
this.library = library;
}

@GetMapping("/patrons")
public GetPatronsResponse getPatrons() {
Set<LibraryGuest> patrons = library.getPatrons();
List<PatronResponse> responsePatrons = patrons.stream().map(PatronResponse::from).toList();
var response = GetPatronsResponse.builder().patrons(responsePatrons).build();
return response;
}

@PostMapping("/patrons")
public CreatePatronResponse postPatron(@Valid @RequestBody CreatePatronRequest req) {
Patron guest = PatronRequest.asPatron(req.getPatron());
library.addLibraryGuest(guest);
return CreatePatronResponse.builder().patron(PatronResponse.from(guest)).build();
}

@GetMapping("/patrons/{id}")
public GetPatronsResponse getPatron(@PathVariable("id") UUID id) {
if (!library.hasLibraryGuest(id)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Guest patron not found");
}
Set<LibraryGuest> patrons = new HashSet<>();
for (LibraryGuest guest : library.getPatrons()) {
if (guest.getId() == id) {
patrons.add(guest);
}
}
List<PatronResponse> responsePatrons = patrons.stream().map(PatronResponse::from).toList();
var response = GetPatronsResponse.builder().patrons(responsePatrons).build();
return response;
}

@DeleteMapping("/patrons/{id}")
public ResponseEntity<Void> deletePatron(@PathVariable() UUID id) {
if (!library.hasLibraryGuest(id)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Guest patron not found");
}
library.removeLibraryGuest(id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.codedifferently.lesson16.web;

import static org.assertj.core.api.Assertions.assertThat;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.codedifferently.lesson16.Lesson16;
import com.codedifferently.lesson16.library.Library;
import com.codedifferently.lesson16.library.LibraryGuest;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

// ___________________________________________________________
// THIS CODE WAS MADE IN COLLABORATION WITH VICENTE AND RICH
// ___________________________________________________________

@SpringBootTest
@ContextConfiguration(classes = Lesson16.class)
class PatronControllerTest {
private static MockMvc mockMvc;
@Autowired private Library library;

private Library lib = library;

@BeforeAll
static void setUp(WebApplicationContext wac) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}

@Test
void testController_getsAnPatron() throws Exception {
List<LibraryGuest> pat = library.getPatrons().stream().toList();
UUID ids = pat.get(3).getId();

mockMvc
.perform(get("/patrons/" + ids.toString()).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

@Test
void testController_getsAllPatrons() throws Exception {
mockMvc
.perform(get("/patrons").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.patrons").isArray())
.andExpect(jsonPath("$.patrons.length()").value(5));
}

@Test
void testController_returnsNotFoundOnGetPatron() throws Exception {
mockMvc
.perform(
get("/patrons/00000000-0000-0000-0000-000000000000")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}

@Test
void testController_reportsBadRequestOnAddPatron() throws Exception {
String json = "{}";

mockMvc
.perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors").isArray())
.andExpect(jsonPath("$.errors.length()").value(1));
}

@Test
void testController_addsPatron() throws Exception {
String json =
"""
{
"patron":{
"name": "John Book",
"email": "johk@reallibrary.org"
}
}
""";

mockMvc
.perform(post("/patrons").contentType(MediaType.APPLICATION_JSON).content(json))
.andExpect(status().isOk())
.andExpect(jsonPath("$.patron.name").value("John Book"));
}

@Test
void testController_returnsNotFoundOnDeletePatron() throws Exception {
mockMvc
.perform(
delete("/patrons/00000000-0000-0000-0000-000000000000")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}

@Test
void testController_deletesPatron() throws Exception {
Library lib = library;
List<LibraryGuest> pat = library.getPatrons().stream().toList();
UUID ids = getGuestId(pat);

mockMvc
.perform(delete("/patrons/" + ids.toString()).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
int i = 0;
pat = library.getPatrons().stream().toList();
for (LibraryGuest guest : pat) {
if (guest.getId() == ids) {
i++;
}
}
library = lib;
assertThat(i).isEqualTo(0);
}

UUID getGuestId(List<LibraryGuest> list) {
for (LibraryGuest guest : list) {
if (guest.getCheckedOutMediaItems().size() == 0) {
return guest.getId();
}
}
return list.get(4).getId();
}
}