Added update method to controller and related implementation.
This commit is contained in:
parent
9b82e549ca
commit
3a7c0f5b1d
@ -4,14 +4,18 @@ import app.mealsmadeeasy.api.auth.AuthService;
|
|||||||
import app.mealsmadeeasy.api.auth.LoginDetails;
|
import app.mealsmadeeasy.api.auth.LoginDetails;
|
||||||
import app.mealsmadeeasy.api.auth.LoginException;
|
import app.mealsmadeeasy.api.auth.LoginException;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
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.recipe.star.RecipeStarService;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
import app.mealsmadeeasy.api.user.UserCreateException;
|
import app.mealsmadeeasy.api.user.UserCreateException;
|
||||||
import app.mealsmadeeasy.api.user.UserService;
|
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.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
@ -40,6 +44,9 @@ public class RecipeControllerTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AuthService authService;
|
private AuthService authService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
private User createTestUser(String username) {
|
private User createTestUser(String username) {
|
||||||
try {
|
try {
|
||||||
return this.userService.createUser(username, username + "@test.com", "test");
|
return this.userService.createUser(username, username + "@test.com", "test");
|
||||||
@ -187,6 +194,65 @@ public class RecipeControllerTests {
|
|||||||
.andExpect(jsonPath("$.content", hasSize(3)));
|
.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("<h1>Hello, Updated World!</h1>"))
|
||||||
|
.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
|
@Test
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
public void addStarToRecipe() throws Exception {
|
public void addStarToRecipe() throws Exception {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
||||||
@ -162,13 +163,18 @@ public class RecipeServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException {
|
public void getByIdWithStarsOkayWhenPublicRecipeWithViewer() throws RecipeException, ImageException {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final User viewer = this.createTestUser("viewer");
|
final User viewer = this.createTestUser("viewer");
|
||||||
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
final Recipe notYetPublicRecipe = this.createTestRecipe(owner);
|
||||||
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec();
|
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(notYetPublicRecipe);
|
||||||
updateSpec.setPublic(true);
|
updateSpec.setIsPublic(true);
|
||||||
final Recipe publicRecipe = this.recipeService.update(notYetPublicRecipe.getId(), updateSpec, owner);
|
final Recipe publicRecipe = this.recipeService.update(
|
||||||
|
notYetPublicRecipe.getOwner().getUsername(),
|
||||||
|
notYetPublicRecipe.getSlug(),
|
||||||
|
updateSpec,
|
||||||
|
owner
|
||||||
|
);
|
||||||
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer));
|
assertDoesNotThrow(() -> this.recipeService.getByIdWithStars(publicRecipe.getId(), viewer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +310,7 @@ public class RecipeServiceTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
public void updateRawText() throws RecipeException {
|
public void updateRawText() throws RecipeException, ImageException {
|
||||||
final User owner = this.createTestUser("recipeOwner");
|
final User owner = this.createTestUser("recipeOwner");
|
||||||
final RecipeCreateSpec createSpec = new RecipeCreateSpec();
|
final RecipeCreateSpec createSpec = new RecipeCreateSpec();
|
||||||
createSpec.setSlug("my-recipe");
|
createSpec.setSlug("my-recipe");
|
||||||
@ -312,9 +318,14 @@ public class RecipeServiceTests {
|
|||||||
createSpec.setRawText("# A Heading");
|
createSpec.setRawText("# A Heading");
|
||||||
Recipe recipe = this.recipeService.create(owner, createSpec);
|
Recipe recipe = this.recipeService.create(owner, createSpec);
|
||||||
final String newRawText = "# A Heading\n## A Subheading";
|
final String newRawText = "# A Heading\n## A Subheading";
|
||||||
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec();
|
final RecipeUpdateSpec updateSpec = new RecipeUpdateSpec(recipe);
|
||||||
updateSpec.setRawText(newRawText);
|
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));
|
assertThat(recipe.getRawText(), is(newRawText));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +339,12 @@ public class RecipeServiceTests {
|
|||||||
updateSpec.setRawText("should fail");
|
updateSpec.setRawText("should fail");
|
||||||
assertThrows(
|
assertThrows(
|
||||||
AccessDeniedException.class,
|
AccessDeniedException.class,
|
||||||
() -> this.recipeService.update(recipe.getId(), updateSpec, notOwner)
|
() -> this.recipeService.update(
|
||||||
|
recipe.getOwner().getUsername(),
|
||||||
|
recipe.getSlug(),
|
||||||
|
updateSpec,
|
||||||
|
notOwner
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,10 @@ package app.mealsmadeeasy.api.image;
|
|||||||
public class ImageException extends Exception {
|
public class ImageException extends Exception {
|
||||||
|
|
||||||
public enum Type {
|
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;
|
private final Type type;
|
||||||
|
@ -17,6 +17,7 @@ public interface ImageService {
|
|||||||
|
|
||||||
Image getById(long id, @Nullable User viewer) throws ImageException;
|
Image getById(long id, @Nullable User viewer) throws ImageException;
|
||||||
Image getByOwnerAndFilename(User owner, String filename, 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;
|
InputStream getImageContent(Image image, @Nullable User viewer) throws IOException;
|
||||||
List<Image> getImagesOwnedBy(User user);
|
List<Image> getImagesOwnedBy(User user);
|
||||||
|
@ -17,4 +17,7 @@ public interface S3ImageRepository extends JpaRepository<S3ImageEntity, Long> {
|
|||||||
List<S3ImageEntity> findAllByOwner(UserEntity owner);
|
List<S3ImageEntity> findAllByOwner(UserEntity owner);
|
||||||
Optional<S3ImageEntity> findByOwnerAndUserFilename(UserEntity owner, String filename);
|
Optional<S3ImageEntity> findByOwnerAndUserFilename(UserEntity owner, String filename);
|
||||||
|
|
||||||
|
@Query("SELECT image from Image image WHERE image.owner.username = ?1 AND image.userFilename = ?2")
|
||||||
|
Optional<S3ImageEntity> findByOwnerUsernameAndFilename(String username, String filename);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
@Override
|
||||||
@PreAuthorize("@imageSecurity.isViewableBy(#image, #viewer)")
|
@PreAuthorize("@imageSecurity.isViewableBy(#image, #viewer)")
|
||||||
public InputStream getImageContent(Image image, User viewer) throws IOException {
|
public InputStream getImageContent(Image image, User viewer) throws IOException {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
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.RecipeStar;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
||||||
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
||||||
@ -42,6 +44,14 @@ public class RecipeController {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) {
|
||||||
|
Map<String, Object> 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}")
|
@GetMapping("/{username}/{slug}")
|
||||||
public ResponseEntity<Map<String, Object>> getByUsernameAndSlug(
|
public ResponseEntity<Map<String, Object>> getByUsernameAndSlug(
|
||||||
@PathVariable String username,
|
@PathVariable String username,
|
||||||
@ -55,11 +65,20 @@ public class RecipeController {
|
|||||||
includeRawText,
|
includeRawText,
|
||||||
viewer
|
viewer
|
||||||
);
|
);
|
||||||
final Map<String, Object> body = new HashMap<>();
|
return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, viewer));
|
||||||
body.put("recipe", view);
|
}
|
||||||
body.put("isStarred", this.recipeService.isStarer(username, slug, viewer));
|
|
||||||
body.put("isOwner", this.recipeService.isOwner(username, slug, viewer));
|
@PostMapping("/{username}/{slug}")
|
||||||
return ResponseEntity.ok(body);
|
public ResponseEntity<Map<String, Object>> 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
|
@GetMapping
|
||||||
|
@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
public interface RecipeSecurity {
|
public interface RecipeSecurity {
|
||||||
boolean isOwner(Recipe recipe, User user);
|
boolean isOwner(Recipe recipe, User user);
|
||||||
boolean isOwner(long recipeId, User user) throws RecipeException;
|
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(Recipe recipe, @Nullable User user) throws RecipeException;
|
||||||
boolean isViewableBy(String ownerUsername, String slug, @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;
|
boolean isViewableBy(long recipeId, @Nullable User user) throws RecipeException;
|
||||||
|
@ -29,6 +29,17 @@ public class RecipeSecurityImpl implements RecipeSecurity {
|
|||||||
return this.isOwner(recipe, user);
|
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
|
@Override
|
||||||
public boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException {
|
public boolean isViewableBy(Recipe recipe, @Nullable User user) throws RecipeException {
|
||||||
if (recipe.isPublic()) {
|
if (recipe.isPublic()) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeCreateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
import app.mealsmadeeasy.api.recipe.view.FullRecipeView;
|
||||||
@ -34,7 +35,8 @@ public interface RecipeService {
|
|||||||
List<Recipe> getRecipesViewableBy(User viewer);
|
List<Recipe> getRecipesViewableBy(User viewer);
|
||||||
List<Recipe> getRecipesOwnedBy(User owner);
|
List<Recipe> 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 addViewer(long id, User modifier, User viewer) throws RecipeException;
|
||||||
Recipe removeViewer(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);
|
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")
|
@Contract("_, _, null -> null")
|
||||||
@Nullable Boolean isStarer(String username, String slug, @Nullable User viewer);
|
@Nullable Boolean isStarer(String username, String slug, @Nullable User viewer);
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.image.Image;
|
import app.mealsmadeeasy.api.image.Image;
|
||||||
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
import app.mealsmadeeasy.api.image.ImageService;
|
import app.mealsmadeeasy.api.image.ImageService;
|
||||||
import app.mealsmadeeasy.api.image.S3ImageEntity;
|
import app.mealsmadeeasy.api.image.S3ImageEntity;
|
||||||
import app.mealsmadeeasy.api.image.view.ImageView;
|
import app.mealsmadeeasy.api.image.view.ImageView;
|
||||||
@ -199,46 +200,37 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PreAuthorize("@recipeSecurity.isOwner(#id, #modifier)")
|
@PreAuthorize("@recipeSecurity.isOwner(#username, #slug, #modifier)")
|
||||||
public Recipe update(long id, RecipeUpdateSpec spec, User modifier) throws RecipeException {
|
public Recipe update(String username, String slug, RecipeUpdateSpec spec, User modifier)
|
||||||
final RecipeEntity entity = this.findRecipeEntity(id);
|
throws RecipeException, ImageException {
|
||||||
boolean didModify = false;
|
final RecipeEntity recipe = this.recipeRepository.findByOwnerUsernameAndSlug(username, slug).orElseThrow(() ->
|
||||||
if (spec.getSlug() != null) {
|
new RecipeException(
|
||||||
entity.setSlug(spec.getSlug());
|
RecipeException.Type.INVALID_USERNAME_OR_SLUG,
|
||||||
didModify = true;
|
"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) {
|
recipe.setMainImage(mainImage);
|
||||||
entity.setTitle(spec.getTitle());
|
|
||||||
didModify = true;
|
recipe.setModified(LocalDateTime.now());
|
||||||
}
|
return this.recipeRepository.save(recipe);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -277,6 +269,16 @@ public class RecipeServiceImpl implements RecipeService {
|
|||||||
this.recipeRepository.deleteById(id);
|
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
|
@Override
|
||||||
@PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)")
|
@PreAuthorize("@recipeSecurity.isViewableBy(#username, #slug, #viewer)")
|
||||||
@Contract("_, _, null -> null")
|
@Contract("_, _, null -> null")
|
||||||
|
@ -1,25 +1,65 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.spec;
|
package app.mealsmadeeasy.api.recipe.spec;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.image.Image;
|
import app.mealsmadeeasy.api.image.Image;
|
||||||
|
import app.mealsmadeeasy.api.recipe.Recipe;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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 {
|
public class RecipeUpdateSpec {
|
||||||
|
|
||||||
private @Nullable String slug;
|
public static class MainImageUpdateSpec {
|
||||||
private @Nullable String title;
|
|
||||||
|
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 preparationTime;
|
||||||
private @Nullable Integer cookingTime;
|
private @Nullable Integer cookingTime;
|
||||||
private @Nullable Integer totalTime;
|
private @Nullable Integer totalTime;
|
||||||
private @Nullable String rawText;
|
private String rawText;
|
||||||
private @Nullable Boolean isPublic;
|
private boolean isPublic;
|
||||||
private @Nullable Image mainImage;
|
private @Nullable MainImageUpdateSpec mainImageUpdateSpec;
|
||||||
|
|
||||||
public @Nullable String getSlug() {
|
public RecipeUpdateSpec() {}
|
||||||
return this.slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
public @Nullable String getTitle() {
|
||||||
@ -62,20 +102,20 @@ public class RecipeUpdateSpec {
|
|||||||
this.rawText = rawText;
|
this.rawText = rawText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Boolean getPublic() {
|
public boolean getIsPublic() {
|
||||||
return this.isPublic;
|
return this.isPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPublic(@Nullable Boolean isPublic) {
|
public void setIsPublic(boolean isPublic) {
|
||||||
this.isPublic = isPublic;
|
this.isPublic = isPublic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable Image getMainImage() {
|
public @Nullable MainImageUpdateSpec getMainImageUpdateSpec() {
|
||||||
return this.mainImage;
|
return this.mainImageUpdateSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMainImage(@Nullable Image mainImage) {
|
public void setMainImageUpdateSpec(@Nullable MainImageUpdateSpec mainImageUpdateSpec) {
|
||||||
this.mainImage = mainImage;
|
this.mainImageUpdateSpec = mainImageUpdateSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user