From 3a7c0f5b1df66baa35458fb39f03c0605861de0e Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Fri, 16 Aug 2024 11:38:00 -0500 Subject: [PATCH] Added update method to controller and related implementation. --- .../api/recipe/RecipeControllerTests.java | 66 +++++++++++++++ .../api/recipe/RecipeServiceTests.java | 32 ++++++-- .../api/image/ImageException.java | 5 +- .../mealsmadeeasy/api/image/ImageService.java | 1 + .../api/image/S3ImageRepository.java | 3 + .../api/image/S3ImageService.java | 11 +++ .../api/recipe/RecipeController.java | 29 +++++-- .../api/recipe/RecipeSecurity.java | 1 + .../api/recipe/RecipeSecurityImpl.java | 11 +++ .../api/recipe/RecipeService.java | 7 +- .../api/recipe/RecipeServiceImpl.java | 80 ++++++++++--------- .../api/recipe/spec/RecipeUpdateSpec.java | 72 +++++++++++++---- 12 files changed, 248 insertions(+), 70 deletions(-) diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java index ffc6a3f..4f5391c 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeControllerTests.java @@ -4,14 +4,18 @@ import app.mealsmadeeasy.api.auth.AuthService; import app.mealsmadeeasy.api.auth.LoginDetails; import app.mealsmadeeasy.api.auth.LoginException; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; +import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStarService; import app.mealsmadeeasy.api.user.User; import app.mealsmadeeasy.api.user.UserCreateException; import app.mealsmadeeasy.api.user.UserService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; @@ -40,6 +44,9 @@ public class RecipeControllerTests { @Autowired private AuthService authService; + @Autowired + private ObjectMapper objectMapper; + private User createTestUser(String username) { try { return this.userService.createUser(username, username + "@test.com", "test"); @@ -187,6 +194,65 @@ public class RecipeControllerTests { .andExpect(jsonPath("$.content", hasSize(3))); } + private String getUpdateBody() throws JsonProcessingException { + final RecipeUpdateSpec spec = new RecipeUpdateSpec(); + spec.setTitle("Updated Test Recipe"); + spec.setPreparationTime(15); + spec.setCookingTime(30); + spec.setTotalTime(45); + spec.setRawText("# Hello, Updated World!"); + spec.setIsPublic(true); + return this.objectMapper.writeValueAsString(spec); + } + + @Test + @DirtiesContext + public void updateRecipe() throws Exception { + final User owner = this.createTestUser("owner"); + final Recipe recipe = this.createTestRecipe(owner, false); + final String accessToken = this.getAccessToken(owner); + final String body = this.getUpdateBody(); + this.mockMvc.perform( + post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug()) + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(body) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.recipe.id").value(recipe.getId())) + .andExpect(jsonPath("$.recipe.title").value("Updated Test Recipe")) + .andExpect(jsonPath("$.recipe.preparationTime").value(15)) + .andExpect(jsonPath("$.recipe.cookingTime").value(30)) + .andExpect(jsonPath("$.recipe.totalTime").value(45)) + .andExpect(jsonPath("$.recipe.text").value("

Hello, Updated World!

")) + .andExpect(jsonPath("$.recipe.rawText").doesNotExist()) + .andExpect(jsonPath("$.recipe.owner.id").value(owner.getId())) + .andExpect(jsonPath("$.recipe.owner.username").value(owner.getUsername())) + .andExpect(jsonPath("$.recipe.starCount").value(0)) + .andExpect(jsonPath("$.recipe.viewerCount").value(0)) + .andExpect(jsonPath("$.recipe.isPublic").value(true)) + .andExpect(jsonPath("$.isStarred").value(false)) + .andExpect(jsonPath("$.isOwner").value(true)); + } + + @Test + @DirtiesContext + public void updateRecipeIncludeRawText() throws Exception { + final User owner = this.createTestUser("owner"); + final Recipe recipe = this.createTestRecipe(owner, false); + final String accessToken = this.getAccessToken(owner); + final String body = this.getUpdateBody(); + this.mockMvc.perform( + post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug()) + .header("Authorization", "Bearer " + accessToken) + .param("includeRawText", "true") + .contentType(MediaType.APPLICATION_JSON) + .content(body) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.recipe.rawText").value("# Hello, Updated World!")); + } + @Test @DirtiesContext public void addStarToRecipe() throws Exception { diff --git a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java index 9ec3f9c..0e89140 100644 --- a/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java +++ b/src/integrationTest/java/app/mealsmadeeasy/api/recipe/RecipeServiceTests.java @@ -1,5 +1,6 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.image.ImageException; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStar; @@ -162,13 +163,18 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException { + public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException, ImageException { final User owner = this.createTestUser("recipeOwner"); final User viewer = this.createTestUser("viewer"); final Recipe notYetPublicRecipe = this.createTestRecipe(owner); - final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(); - updateSpec.setPublic(true); - final Recipe publicRecipe = this.recipeService.update(notYetPublicRecipe.getId(), updateSpec, owner); + final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(notYetPublicRecipe); + updateSpec.setIsPublic(true); + final Recipe publicRecipe = this.recipeService.update( + notYetPublicRecipe.getOwner().getUsername(), + notYetPublicRecipe.getSlug(), + updateSpec, + owner + ); assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer)); } @@ -304,7 +310,7 @@ public class RecipeServiceTests { @Test @DirtiesContext - public void updateRawText() throws RecipeException { + public void updateRawText() throws RecipeException, ImageException { final User owner = this.createTestUser("recipeOwner"); final RecipeCreateSpec createSpec = new RecipeCreateSpec(); createSpec.setSlug("my-recipe"); @@ -312,9 +318,14 @@ public class RecipeServiceTests { createSpec.setRawText("# A Heading"); Recipe recipe = this.recipeService.create(owner, createSpec); final String newRawText = "# A Heading\n## A Subheading"; - final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(); + final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(recipe); updateSpec.setRawText(newRawText); - recipe = this.recipeService.update(recipe.getId(), updateSpec, owner); + recipe = this.recipeService.update( + recipe.getOwner().getUsername(), + recipe.getSlug(), + updateSpec, + owner + ); assertThat(recipe.getRawText(), is(newRawText)); } @@ -328,7 +339,12 @@ public class RecipeServiceTests { updateSpec.setRawText("should fail"); assertThrows( AccessDeniedException.class, - () -> this.recipeService.update(recipe.getId(), updateSpec, notOwner) + () -> this.recipeService.update( + recipe.getOwner().getUsername(), + recipe.getSlug(), + updateSpec, + notOwner + ) ); } diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageException.java b/src/main/java/app/mealsmadeeasy/api/image/ImageException.java index 9e0844c..50d754c 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageException.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageException.java @@ -3,7 +3,10 @@ package app.mealsmadeeasy.api.image; public class ImageException extends Exception { public enum Type { - INVALID_ID, IMAGE_NOT_FOUND, UNKNOWN_MIME_TYPE + INVALID_ID, + INVALID_USERNAME_OR_FILENAME, + IMAGE_NOT_FOUND, + UNKNOWN_MIME_TYPE } private final Type type; diff --git a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java index 3f0b376..a69d422 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/ImageService.java @@ -17,6 +17,7 @@ public interface ImageService { Image getById(long id, @Nullable User viewer) throws ImageException; Image getByOwnerAndFilename(User owner, String filename, User viewer) throws ImageException; + Image getByUsernameAndFilename(String username, String filename, User viewer) throws ImageException; InputStream getImageContent(Image image, @Nullable User viewer) throws IOException; List getImagesOwnedBy(User user); diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java b/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java index e79cd23..5bde6b6 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java +++ b/src/main/java/app/mealsmadeeasy/api/image/S3ImageRepository.java @@ -17,4 +17,7 @@ public interface S3ImageRepository extends JpaRepository { List findAllByOwner(UserEntity owner); Optional findByOwnerAndUserFilename(UserEntity owner, String filename); + @Query("SELECT image from Image image WHERE image.owner.username = ?1 AND image.userFilename = ?2") + Optional findByOwnerUsernameAndFilename(String username, String filename); + } diff --git a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java index d1e2a64..33dd05a 100644 --- a/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java +++ b/src/main/java/app/mealsmadeeasy/api/image/S3ImageService.java @@ -139,6 +139,17 @@ public class S3ImageService implements ImageService { )); } + @Override + @PostAuthorize("@imageSecurity.isViewableBy(returnObject, #viewer)") + public Image getByUsernameAndFilename(String username, String filename, User viewer) throws ImageException { + return this.imageRepository.findByOwnerUsernameAndFilename(username, filename).orElseThrow( + () -> new ImageException( + ImageException.Type.INVALID_USERNAME_OR_FILENAME, + "No such Image for username " + username + " and filename " + filename + ) + ); + } + @Override @PreAuthorize("@imageSecurity.isViewableBy(#image, #viewer)") public InputStream getImageContent(Image image, User viewer) throws IOException { diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java index c011b9c..a36332c 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeController.java @@ -1,5 +1,7 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.image.ImageException; +import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.star.RecipeStar; import app.mealsmadeeasy.api.recipe.star.RecipeStarService; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; @@ -42,6 +44,14 @@ public class RecipeController { )); } + private Map getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) { + Map wrapper = new HashMap<>(); + wrapper.put("recipe", view); + wrapper.put("isStarred", this.recipeService.isStarer(username, slug, viewer)); + wrapper.put("isOwner", this.recipeService.isOwner(username, slug, viewer)); + return wrapper; + } + @GetMapping("/{username}/{slug}") public ResponseEntity> getByUsernameAndSlug( @PathVariable String username, @@ -55,11 +65,20 @@ public class RecipeController { includeRawText, viewer ); - final Map body = new HashMap<>(); - body.put("recipe", view); - body.put("isStarred", this.recipeService.isStarer(username, slug, viewer)); - body.put("isOwner", this.recipeService.isOwner(username, slug, viewer)); - return ResponseEntity.ok(body); + return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, viewer)); + } + + @PostMapping("/{username}/{slug}") + public ResponseEntity> updateByUsernameAndSlug( + @PathVariable String username, + @PathVariable String slug, + @RequestParam(defaultValue = "false") boolean includeRawText, + @RequestBody RecipeUpdateSpec updateSpec, + @AuthenticationPrincipal User principal + ) throws ImageException, RecipeException { + final Recipe updated = this.recipeService.update(username, slug, updateSpec, principal); + final FullRecipeView view = this.recipeService.toFullRecipeView(updated, includeRawText, principal); + return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal)); } @GetMapping diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java index 5960f64..99f43a6 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurity.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; public interface RecipeSecurity { boolean isOwner(Recipe recipe, User user); boolean isOwner(long recipeId, User user) throws RecipeException; + boolean isOwner(String username, String slug, @Nullable User user) throws RecipeException; boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException; boolean isViewableBy(String ownerUsername, String slug, @Nullable User user) throws RecipeException; boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException; diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java index de87b84..03af8d1 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeSecurityImpl.java @@ -29,6 +29,17 @@ public class RecipeSecurityImpl implements RecipeSecurity { return this.isOwner(recipe, user); } + @Override + public boolean isOwner(String username, String slug, @Nullable User user) throws RecipeException { + final Recipe recipe = this.recipeRepository.findByOwnerUsernameAndSlug(username, slug).orElseThrow( + () -> new RecipeException( + RecipeException.Type.INVALID_USERNAME_OR_SLUG, + "No such Recipe for username " + username + " and slug " + slug + ) + ); + return this.isOwner(recipe, user); + } + @Override public boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException { if (recipe.isPublic()) { diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java index e7724b0..562a074 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeService.java @@ -1,5 +1,6 @@ package app.mealsmadeeasy.api.recipe; +import app.mealsmadeeasy.api.image.ImageException; import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec; import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec; import app.mealsmadeeasy.api.recipe.view.FullRecipeView; @@ -34,7 +35,8 @@ public interface RecipeService { List getRecipesViewableBy(User viewer); List getRecipesOwnedBy(User owner); - Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException; + Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier) + throws RecipeException, ImageException; Recipe addViewer(long id, User modifier, User viewer) throws RecipeException; Recipe removeViewer(long id, User modifier, User viewer) throws RecipeException; @@ -42,6 +44,9 @@ public interface RecipeService { void deleteRecipe(long id, User modifier); + FullRecipeView toFullRecipeView(Recipe recipe, boolean includeRawText, @Nullable User viewer); + RecipeInfoView toRecipeInfoView(Recipe recipe, @Nullable User viewer); + @Contract("_, _, null -> null") @Nullable Boolean isStarer(String username, String slug, @Nullable User viewer); diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java index 6a7ce73..eefb6e9 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/RecipeServiceImpl.java @@ -1,6 +1,7 @@ package app.mealsmadeeasy.api.recipe; import app.mealsmadeeasy.api.image.Image; +import app.mealsmadeeasy.api.image.ImageException; import app.mealsmadeeasy.api.image.ImageService; import app.mealsmadeeasy.api.image.S3ImageEntity; import app.mealsmadeeasy.api.image.view.ImageView; @@ -199,46 +200,37 @@ public class RecipeServiceImpl implements RecipeService { } @Override - @PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)") - public Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException { - final RecipeEntity entity = this.findRecipeEntity(id); - boolean didModify = false; - if (spec.getSlug() != null) { - entity.setSlug(spec.getSlug()); - didModify = true; + @PreAuthorize("@recipeSecurity.isOwner(#username, #slug, #modifier)") + public Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier) + throws RecipeException, ImageException { + final RecipeEntity recipe = this.recipeRepository.findByOwnerUsernameAndSlug(username, slug).orElseThrow(() -> + new RecipeException( + RecipeException.Type.INVALID_USERNAME_OR_SLUG, + "No such Recipe for username " + username + " and slug: " + slug + ) + ); + + recipe.setTitle(spec.getTitle()); + recipe.setPreparationTime(spec.getPreparationTime()); + recipe.setCookingTime(spec.getCookingTime()); + recipe.setTotalTime(spec.getTotalTime()); + recipe.setRawText(spec.getRawText()); + recipe.setPublic(spec.getIsPublic()); + + final S3ImageEntity mainImage; + if (spec.getMainImageUpdateSpec() == null) { + mainImage = null; + } else { + mainImage = (S3ImageEntity) this.imageService.getByUsernameAndFilename( + spec.getMainImageUpdateSpec().getUsername(), + spec.getMainImageUpdateSpec().getFilename(), + modifier + ); } - if (spec.getTitle() != null) { - entity.setTitle(spec.getTitle()); - didModify = true; - } - if (spec.getPreparationTime() != null) { - entity.setPreparationTime(spec.getPreparationTime()); - didModify = true; - } - if (spec.getCookingTime() != null) { - entity.setCookingTime(spec.getCookingTime()); - didModify = true; - } - if (spec.getTotalTime() != null) { - entity.setTotalTime(spec.getTotalTime()); - didModify = true; - } - if (spec.getRawText() != null) { - entity.setRawText(spec.getRawText()); - didModify = true; - } - if (spec.getPublic() != null) { - entity.setPublic(spec.getPublic()); - didModify = true; - } - if (spec.getMainImage() != null) { - entity.setMainImage((S3ImageEntity) spec.getMainImage()); - didModify = true; - } - if (didModify) { - entity.setModified(LocalDateTime.now()); - } - return this.recipeRepository.save(entity); + recipe.setMainImage(mainImage); + + recipe.setModified(LocalDateTime.now()); + return this.recipeRepository.save(recipe); } @Override @@ -277,6 +269,16 @@ public class RecipeServiceImpl implements RecipeService { this.recipeRepository.deleteById(id); } + @Override + public FullRecipeView toFullRecipeView(Recipe recipe, boolean includeRawText, @Nullable User viewer) { + return this.getFullView((RecipeEntity) recipe, includeRawText, viewer); + } + + @Override + public RecipeInfoView toRecipeInfoView(Recipe recipe, @Nullable User viewer) { + return this.getInfoView((RecipeEntity) recipe, viewer); + } + @Override @PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)") @Contract("_, _, null -> null") diff --git a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java index 0c9eb44..1bf8293 100644 --- a/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java +++ b/src/main/java/app/mealsmadeeasy/api/recipe/spec/RecipeUpdateSpec.java @@ -1,25 +1,65 @@ package app.mealsmadeeasy.api.recipe.spec; import app.mealsmadeeasy.api.image.Image; +import app.mealsmadeeasy.api.recipe.Recipe; import org.jetbrains.annotations.Nullable; +// For now, we cannot change slug after creation. +// In the future, we may be able to have redirects from +// old slugs to new slugs. public class RecipeUpdateSpec { - private @Nullable String slug; - private @Nullable String title; + public static class MainImageUpdateSpec { + + private String username; + private String filename; + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFilename() { + return this.filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + } + + private String title; private @Nullable Integer preparationTime; private @Nullable Integer cookingTime; private @Nullable Integer totalTime; - private @Nullable String rawText; - private @Nullable Boolean isPublic; - private @Nullable Image mainImage; + private String rawText; + private boolean isPublic; + private @Nullable MainImageUpdateSpec mainImageUpdateSpec; - public @Nullable String getSlug() { - return this.slug; - } + public RecipeUpdateSpec() {} - public void setSlug(@Nullable String slug) { - this.slug = slug; + /** + * Convenience constructor for testing purposes. + * + * @param recipe the Recipe to copy from + */ + public RecipeUpdateSpec(Recipe recipe) { + this.title = recipe.getTitle(); + this.preparationTime = recipe.getPreparationTime(); + this.cookingTime = recipe.getCookingTime(); + this.totalTime = recipe.getTotalTime(); + this.rawText = recipe.getRawText(); + this.isPublic = recipe.isPublic(); + final @Nullable Image mainImage = recipe.getMainImage(); + if (mainImage != null) { + this.mainImageUpdateSpec = new MainImageUpdateSpec(); + this.mainImageUpdateSpec.setUsername(mainImage.getOwner().getUsername()); + this.mainImageUpdateSpec.setFilename(mainImage.getUserFilename()); + } } public @Nullable String getTitle() { @@ -62,20 +102,20 @@ public class RecipeUpdateSpec { this.rawText = rawText; } - public @Nullable Boolean getPublic() { + public boolean getIsPublic() { return this.isPublic; } - public void setPublic(@Nullable Boolean isPublic) { + public void setIsPublic(boolean isPublic) { this.isPublic = isPublic; } - public @Nullable Image getMainImage() { - return this.mainImage; + public @Nullable MainImageUpdateSpec getMainImageUpdateSpec() { + return this.mainImageUpdateSpec; } - public void setMainImage(@Nullable Image mainImage) { - this.mainImage = mainImage; + public void setMainImageUpdateSpec(@Nullable MainImageUpdateSpec mainImageUpdateSpec) { + this.mainImageUpdateSpec = mainImageUpdateSpec; } }