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