MME-9 Add ingredients to Recipe entity.
This commit is contained in:
parent
a92084a35a
commit
98c89f6247
@ -1,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.MinIOTestsExtension;
|
||||||
import app.mealsmadeeasy.api.PostgresTestsExtension;
|
import app.mealsmadeeasy.api.PostgresTestsExtension;
|
||||||
import app.mealsmadeeasy.api.image.Image;
|
import app.mealsmadeeasy.api.image.Image;
|
||||||
import app.mealsmadeeasy.api.image.ImageException;
|
import app.mealsmadeeasy.api.image.ImageException;
|
||||||
@ -36,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||||||
// TODO: test prep/cooking/total times included
|
// TODO: test prep/cooking/total times included
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@ExtendWith(PostgresTestsExtension.class)
|
@ExtendWith(PostgresTestsExtension.class)
|
||||||
|
@ExtendWith(MinIOTestsExtension.class)
|
||||||
public class RecipeServiceTests {
|
public class RecipeServiceTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -107,16 +109,31 @@ public class RecipeServiceTests {
|
|||||||
public void whenCreate_allFieldsTransferredFromSpec() throws Exception {
|
public void whenCreate_allFieldsTransferredFromSpec() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.seedUser();
|
||||||
final Image image = this.seedImage(owner);
|
final Image image = this.seedImage(owner);
|
||||||
|
|
||||||
|
final List<RecipeCreateSpec.IngredientCreateSpec> ingredients = List.of(
|
||||||
|
RecipeCreateSpec.IngredientCreateSpec.builder()
|
||||||
|
.amount("1T")
|
||||||
|
.name("Butter")
|
||||||
|
.notes("softened")
|
||||||
|
.build(),
|
||||||
|
RecipeCreateSpec.IngredientCreateSpec.builder()
|
||||||
|
.amount("2T")
|
||||||
|
.name("Shortening")
|
||||||
|
.notes("vegan")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
final RecipeCreateSpec spec = RecipeCreateSpec.builder()
|
final RecipeCreateSpec spec = RecipeCreateSpec.builder()
|
||||||
.title("Recipe Title")
|
.title("Recipe Title")
|
||||||
.slug("recipe-slug")
|
.slug("recipe-slug")
|
||||||
.preparationTime(15)
|
.preparationTime(15)
|
||||||
.cookingTime(30)
|
.cookingTime(30)
|
||||||
.totalTime(45)
|
.totalTime(45)
|
||||||
|
.ingredients(ingredients)
|
||||||
.rawText("# Hello Recipe")
|
.rawText("# Hello Recipe")
|
||||||
.isPublic(true)
|
.isPublic(true)
|
||||||
.mainImage(image)
|
.mainImage(image)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final Recipe recipe = this.recipeService.create(owner, spec, false);
|
final Recipe recipe = this.recipeService.create(owner, spec, false);
|
||||||
assertThat(recipe.getId(), is(notNullValue()));
|
assertThat(recipe.getId(), is(notNullValue()));
|
||||||
assertThat(recipe.getTitle(), is("Recipe Title"));
|
assertThat(recipe.getTitle(), is("Recipe Title"));
|
||||||
@ -124,6 +141,8 @@ public class RecipeServiceTests {
|
|||||||
assertThat(recipe.getPreparationTime(), is(15));
|
assertThat(recipe.getPreparationTime(), is(15));
|
||||||
assertThat(recipe.getCookingTime(), is(30));
|
assertThat(recipe.getCookingTime(), is(30));
|
||||||
assertThat(recipe.getTotalTime(), is(45));
|
assertThat(recipe.getTotalTime(), is(45));
|
||||||
|
assertThat(recipe.getIngredients(), is(notNullValue()));
|
||||||
|
assertThat(recipe.getIngredients().size(), is(2));
|
||||||
assertThat(recipe.getRawText(), is("# Hello Recipe"));
|
assertThat(recipe.getRawText(), is("# Hello Recipe"));
|
||||||
assertThat(recipe.getMainImage(), is(notNullValue()));
|
assertThat(recipe.getMainImage(), is(notNullValue()));
|
||||||
assertThat(recipe.getMainImage().getId(), is(image.getId()));
|
assertThat(recipe.getMainImage().getId(), is(image.getId()));
|
||||||
@ -305,6 +324,61 @@ public class RecipeServiceTests {
|
|||||||
assertThat(viewableInfos, containsRecipes(r0, r1));
|
assertThat(viewableInfos, containsRecipes(r0, r1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenUpdate_allFieldsTransferred() throws Exception {
|
||||||
|
final User owner = this.seedUser();
|
||||||
|
final Recipe base = this.createTestRecipe(owner);
|
||||||
|
|
||||||
|
final List<RecipeUpdateSpec.IngredientUpdateSpec> ingredients = List.of(
|
||||||
|
RecipeUpdateSpec.IngredientUpdateSpec.builder()
|
||||||
|
.amount("1T")
|
||||||
|
.name("Butter")
|
||||||
|
.notes("Softened")
|
||||||
|
.build(),
|
||||||
|
RecipeUpdateSpec.IngredientUpdateSpec.builder()
|
||||||
|
.amount("2T")
|
||||||
|
.name("Shortening")
|
||||||
|
.notes("Vegan")
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
|
||||||
|
final Image image = this.seedImage(owner);
|
||||||
|
final RecipeUpdateSpec.MainImageUpdateSpec mainImageSpec = RecipeUpdateSpec.MainImageUpdateSpec.builder()
|
||||||
|
.username(image.getOwner().getUsername())
|
||||||
|
.filename(image.getUserFilename())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final RecipeUpdateSpec spec = RecipeUpdateSpec.builder()
|
||||||
|
.title("New Title")
|
||||||
|
.preparationTime(20)
|
||||||
|
.cookingTime(40)
|
||||||
|
.totalTime(60)
|
||||||
|
.ingredients(ingredients)
|
||||||
|
.rawText("Updated text.")
|
||||||
|
.isPublic(true)
|
||||||
|
.mainImage(mainImageSpec)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final Recipe updated = this.recipeService.update(
|
||||||
|
base.getOwner().getUsername(),
|
||||||
|
base.getSlug(),
|
||||||
|
spec,
|
||||||
|
owner
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(updated.getId(), is(base.getId()));
|
||||||
|
assertThat(updated.getTitle(), is("New Title"));
|
||||||
|
assertThat(updated.getSlug(), is(base.getSlug()));
|
||||||
|
assertThat(updated.getPreparationTime(), is(20));
|
||||||
|
assertThat(updated.getCookingTime(), is(40));
|
||||||
|
assertThat(updated.getTotalTime(), is(60));
|
||||||
|
assertThat(updated.getIngredients(), is(notNullValue()));
|
||||||
|
assertThat(updated.getIngredients().size(), is(2));
|
||||||
|
assertThat(updated.getIsPublic(), is(true));
|
||||||
|
assertThat(updated.getMainImage(), is(notNullValue()));
|
||||||
|
assertThat(updated.getMainImage().getId(), is(image.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateRawText() {
|
public void updateRawText() {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.seedUser();
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.recipe;
|
package app.mealsmadeeasy.api.recipe;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.MinIOTestsExtension;
|
||||||
import app.mealsmadeeasy.api.PostgresTestsExtension;
|
import app.mealsmadeeasy.api.PostgresTestsExtension;
|
||||||
import app.mealsmadeeasy.api.auth.AuthService;
|
import app.mealsmadeeasy.api.auth.AuthService;
|
||||||
import app.mealsmadeeasy.api.auth.LoginDetails;
|
import app.mealsmadeeasy.api.auth.LoginDetails;
|
||||||
@ -25,6 +26,7 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
@ -35,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ExtendWith(PostgresTestsExtension.class)
|
@ExtendWith(PostgresTestsExtension.class)
|
||||||
|
@ExtendWith(MinIOTestsExtension.class)
|
||||||
public class RecipesControllerTests {
|
public class RecipesControllerTests {
|
||||||
|
|
||||||
private static InputStream getHal9000() {
|
private static InputStream getHal9000() {
|
||||||
@ -222,13 +225,39 @@ public class RecipesControllerTests {
|
|||||||
public void updateRecipe() throws Exception {
|
public void updateRecipe() throws Exception {
|
||||||
final User owner = this.seedUser();
|
final User owner = this.seedUser();
|
||||||
final Recipe recipe = this.createTestRecipe(owner, false);
|
final Recipe recipe = this.createTestRecipe(owner, false);
|
||||||
final String accessToken = this.getAccessToken(owner);
|
final Image image = this.createHal9000(owner);
|
||||||
final String body = this.getUpdateBody();
|
|
||||||
|
final RecipeUpdateSpec spec = RecipeUpdateSpec.builder()
|
||||||
|
.title("Updated Test Recipe")
|
||||||
|
.preparationTime(15)
|
||||||
|
.cookingTime(30)
|
||||||
|
.totalTime(45)
|
||||||
|
.ingredients(List.of(
|
||||||
|
RecipeUpdateSpec.IngredientUpdateSpec.builder()
|
||||||
|
.amount("1T")
|
||||||
|
.name("Butter")
|
||||||
|
.notes("Softened")
|
||||||
|
.build(),
|
||||||
|
RecipeUpdateSpec.IngredientUpdateSpec.builder()
|
||||||
|
.amount("2T")
|
||||||
|
.name("Shortening")
|
||||||
|
.notes("Vegan")
|
||||||
|
.build()
|
||||||
|
))
|
||||||
|
.rawText("# Hello, Updated World!")
|
||||||
|
.isPublic(true)
|
||||||
|
.mainImage(RecipeUpdateSpec.MainImageUpdateSpec.builder()
|
||||||
|
.username(image.getOwner().getUsername())
|
||||||
|
.filename(image.getUserFilename())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
final String updateBody = this.objectMapper.writeValueAsString(spec);
|
||||||
|
|
||||||
this.mockMvc.perform(
|
this.mockMvc.perform(
|
||||||
post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug())
|
post("/recipes/{username}/{slug}", owner.getUsername(), recipe.getSlug())
|
||||||
.header("Authorization", "Bearer " + accessToken)
|
.header("Authorization", "Bearer " + this.getAccessToken(owner))
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(body)
|
.content(updateBody)
|
||||||
)
|
)
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.recipe.id").value(recipe.getId()))
|
.andExpect(jsonPath("$.recipe.id").value(recipe.getId()))
|
||||||
@ -236,6 +265,15 @@ public class RecipesControllerTests {
|
|||||||
.andExpect(jsonPath("$.recipe.preparationTime").value(15))
|
.andExpect(jsonPath("$.recipe.preparationTime").value(15))
|
||||||
.andExpect(jsonPath("$.recipe.cookingTime").value(30))
|
.andExpect(jsonPath("$.recipe.cookingTime").value(30))
|
||||||
.andExpect(jsonPath("$.recipe.totalTime").value(45))
|
.andExpect(jsonPath("$.recipe.totalTime").value(45))
|
||||||
|
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients").isArray())
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[0].amount").value("1T"))
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[0].name").value("Butter"))
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[0].notes").value("Softened"))
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[1].amount").value("2T"))
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[1].name").value("Shortening"))
|
||||||
|
.andExpect(jsonPath("$.recipe.ingredients[1].notes").value("Vegan"))
|
||||||
|
|
||||||
.andExpect(jsonPath("$.recipe.text").value("<h1>Hello, Updated World!</h1>"))
|
.andExpect(jsonPath("$.recipe.text").value("<h1>Hello, Updated World!</h1>"))
|
||||||
.andExpect(jsonPath("$.recipe.rawText").value("# Hello, Updated World!"))
|
.andExpect(jsonPath("$.recipe.rawText").value("# Hello, Updated World!"))
|
||||||
.andExpect(jsonPath("$.recipe.owner.id").value(owner.getId()))
|
.andExpect(jsonPath("$.recipe.owner.id").value(owner.getId()))
|
||||||
@ -243,7 +281,10 @@ public class RecipesControllerTests {
|
|||||||
.andExpect(jsonPath("$.recipe.starCount").value(0))
|
.andExpect(jsonPath("$.recipe.starCount").value(0))
|
||||||
.andExpect(jsonPath("$.recipe.viewerCount").value(0))
|
.andExpect(jsonPath("$.recipe.viewerCount").value(0))
|
||||||
.andExpect(jsonPath("$.recipe.public").value(true))
|
.andExpect(jsonPath("$.recipe.public").value(true))
|
||||||
.andExpect(jsonPath("$.recipe.mainImage").value(nullValue()))
|
|
||||||
|
.andExpect(jsonPath("$.recipe.mainImage.owner.username").value(owner.getUsername()))
|
||||||
|
.andExpect(jsonPath("$.recipe.mainImage.filename").value(image.getUserFilename()))
|
||||||
|
|
||||||
.andExpect(jsonPath("$.isStarred").value(false))
|
.andExpect(jsonPath("$.isStarred").value(false))
|
||||||
.andExpect(jsonPath("$.isOwner").value(true));
|
.andExpect(jsonPath("$.isOwner").value(true));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,17 @@ import app.mealsmadeeasy.api.image.Image;
|
|||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeComment;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStar;
|
||||||
import app.mealsmadeeasy.api.user.User;
|
import app.mealsmadeeasy.api.user.User;
|
||||||
|
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -21,20 +24,54 @@ import java.util.Set;
|
|||||||
@ToString
|
@ToString
|
||||||
public class Recipe {
|
public class Recipe {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public static class Ingredient {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String amount;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o instanceof Ingredient other) {
|
||||||
|
return Objects.equals(this.amount, other.amount)
|
||||||
|
&& Objects.equals(this.name, other.name)
|
||||||
|
&& Objects.equals(this.notes, other.notes);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.amount, this.name, this.notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(nullable = false, updatable = false)
|
@Basic(optional = false)
|
||||||
|
@Column(updatable = false)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Basic(optional = false)
|
||||||
private OffsetDateTime created;
|
private OffsetDateTime created;
|
||||||
|
|
||||||
private OffsetDateTime modified;
|
private OffsetDateTime modified;
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
@Basic(optional = false)
|
||||||
|
@Column(unique = true)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Basic(optional = false)
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -46,7 +83,13 @@ public class Recipe {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private Integer totalTime;
|
private Integer totalTime;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT", nullable = false)
|
@Type(JsonBinaryType.class)
|
||||||
|
@Column(columnDefinition = "JSONB")
|
||||||
|
@Nullable
|
||||||
|
private List<Ingredient> ingredients;
|
||||||
|
|
||||||
|
@Basic(optional = false)
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
private String rawText;
|
private String rawText;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
@ -63,7 +106,7 @@ public class Recipe {
|
|||||||
@OneToMany(mappedBy = "recipe", orphanRemoval = true, cascade = CascadeType.ALL)
|
@OneToMany(mappedBy = "recipe", orphanRemoval = true, cascade = CascadeType.ALL)
|
||||||
private Set<RecipeComment> comments = new HashSet<>();
|
private Set<RecipeComment> comments = new HashSet<>();
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Basic(optional = false)
|
||||||
private Boolean isPublic = false;
|
private Boolean isPublic = false;
|
||||||
|
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
|
|||||||
@ -50,6 +50,20 @@ public class RecipeService {
|
|||||||
draft.setOwner(owner);
|
draft.setOwner(owner);
|
||||||
draft.setSlug(spec.getSlug());
|
draft.setSlug(spec.getSlug());
|
||||||
draft.setTitle(spec.getTitle());
|
draft.setTitle(spec.getTitle());
|
||||||
|
|
||||||
|
if (spec.getIngredients() != null) {
|
||||||
|
draft.setIngredients(spec.getIngredients().stream()
|
||||||
|
.map(ingredientSpec -> {
|
||||||
|
final Recipe.Ingredient ingredient = new Recipe.Ingredient();
|
||||||
|
ingredient.setAmount(ingredientSpec.amount());
|
||||||
|
ingredient.setName(ingredientSpec.name());
|
||||||
|
ingredient.setNotes(ingredientSpec.notes());
|
||||||
|
return ingredient;
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
draft.setRawText(spec.getRawText());
|
draft.setRawText(spec.getRawText());
|
||||||
draft.setMainImage(spec.getMainImage());
|
draft.setMainImage(spec.getMainImage());
|
||||||
draft.setIsPublic(spec.isPublic());
|
draft.setIsPublic(spec.isPublic());
|
||||||
@ -57,12 +71,14 @@ public class RecipeService {
|
|||||||
draft.setCookingTime(spec.getCookingTime());
|
draft.setCookingTime(spec.getCookingTime());
|
||||||
draft.setTotalTime(spec.getTotalTime());
|
draft.setTotalTime(spec.getTotalTime());
|
||||||
final Recipe saved = this.recipeRepository.save(draft);
|
final Recipe saved = this.recipeRepository.save(draft);
|
||||||
|
|
||||||
if (queueSummaryJob) {
|
if (queueSummaryJob) {
|
||||||
this.jobService.create(
|
this.jobService.create(
|
||||||
RecipeSummaryJobHandler.JOB_KEY,
|
RecipeSummaryJobHandler.JOB_KEY,
|
||||||
new RecipeSummaryJobHandler.RecipeSummaryJobPayload(saved.getId())
|
new RecipeSummaryJobHandler.RecipeSummaryJobPayload(saved.getId())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return saved;
|
return saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +176,19 @@ public class RecipeService {
|
|||||||
recipe.setTotalTime(spec.getTotalTime());
|
recipe.setTotalTime(spec.getTotalTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spec.getIngredients() != null) {
|
||||||
|
recipe.setIngredients(spec.getIngredients().stream()
|
||||||
|
.map(ingredientSpec -> {
|
||||||
|
final Recipe.Ingredient ingredient = new Recipe.Ingredient();
|
||||||
|
ingredient.setAmount(ingredientSpec.amount());
|
||||||
|
ingredient.setName(ingredientSpec.name());
|
||||||
|
ingredient.setNotes(ingredientSpec.notes());
|
||||||
|
return ingredient;
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (spec.getRawText() != null) {
|
if (spec.getRawText() != null) {
|
||||||
recipe.setRawText(spec.getRawText());
|
recipe.setRawText(spec.getRawText());
|
||||||
recipe.setCachedRenderedText(null);
|
recipe.setCachedRenderedText(null);
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import app.mealsmadeeasy.api.recipe.comment.RecipeCommentService;
|
|||||||
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView;
|
import app.mealsmadeeasy.api.recipe.comment.RecipeCommentView;
|
||||||
import app.mealsmadeeasy.api.recipe.converter.RecipeToFullViewConverter;
|
import app.mealsmadeeasy.api.recipe.converter.RecipeToFullViewConverter;
|
||||||
import app.mealsmadeeasy.api.recipe.converter.RecipeToInfoViewConverter;
|
import app.mealsmadeeasy.api.recipe.converter.RecipeToInfoViewConverter;
|
||||||
|
import app.mealsmadeeasy.api.recipe.converter.RecipeUpdateBodyToSpecConverter;
|
||||||
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;
|
||||||
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
import app.mealsmadeeasy.api.recipe.star.RecipeStarService;
|
||||||
@ -43,6 +44,7 @@ public class RecipesController {
|
|||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final RecipeToFullViewConverter recipeToFullViewConverter;
|
private final RecipeToFullViewConverter recipeToFullViewConverter;
|
||||||
private final RecipeToInfoViewConverter recipeToInfoViewConverter;
|
private final RecipeToInfoViewConverter recipeToInfoViewConverter;
|
||||||
|
private final RecipeUpdateBodyToSpecConverter updateBodyToSpecConverter;
|
||||||
|
|
||||||
private Map<String, Object> getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) {
|
private Map<String, Object> getFullViewWrapper(String username, String slug, FullRecipeView view, @Nullable User viewer) {
|
||||||
Map<String, Object> wrapper = new HashMap<>();
|
Map<String, Object> wrapper = new HashMap<>();
|
||||||
@ -72,7 +74,7 @@ public class RecipesController {
|
|||||||
@RequestBody RecipeUpdateBody updateBody,
|
@RequestBody RecipeUpdateBody updateBody,
|
||||||
@AuthenticationPrincipal User principal
|
@AuthenticationPrincipal User principal
|
||||||
) {
|
) {
|
||||||
final RecipeUpdateSpec spec = RecipeUpdateSpec.from(updateBody);
|
final RecipeUpdateSpec spec = this.updateBodyToSpecConverter.convert(updateBody);
|
||||||
final Recipe updated = this.recipeService.update(username, slug, spec, principal);
|
final Recipe updated = this.recipeService.update(username, slug, spec, principal);
|
||||||
final FullRecipeView view = this.recipeToFullViewConverter.convert(updated, includeRawText, principal);
|
final FullRecipeView view = this.recipeToFullViewConverter.convert(updated, includeRawText, principal);
|
||||||
return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal));
|
return ResponseEntity.ok(this.getFullViewWrapper(username, slug, view, principal));
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package app.mealsmadeeasy.api.recipe.body;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class RecipeUpdateBody {
|
public class RecipeUpdateBody {
|
||||||
|
|
||||||
@ -12,10 +14,18 @@ public class RecipeUpdateBody {
|
|||||||
private String filename;
|
private String filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class IngredientUpdateBody {
|
||||||
|
private @Nullable String amount;
|
||||||
|
private String name;
|
||||||
|
private @Nullable String notes;
|
||||||
|
}
|
||||||
|
|
||||||
private @Nullable String title;
|
private @Nullable 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 List<IngredientUpdateBody> ingredients;
|
||||||
private @Nullable String rawText;
|
private @Nullable String rawText;
|
||||||
private @Nullable Boolean isPublic;
|
private @Nullable Boolean isPublic;
|
||||||
private @Nullable MainImageUpdateBody mainImage;
|
private @Nullable MainImageUpdateBody mainImage;
|
||||||
|
|||||||
@ -32,12 +32,26 @@ public class RecipeToFullViewConverter {
|
|||||||
.starCount(this.recipeService.getStarCount(recipe))
|
.starCount(this.recipeService.getStarCount(recipe))
|
||||||
.viewerCount(this.recipeService.getViewerCount(recipe))
|
.viewerCount(this.recipeService.getViewerCount(recipe))
|
||||||
.isPublic(recipe.getIsPublic());
|
.isPublic(recipe.getIsPublic());
|
||||||
|
|
||||||
|
if (recipe.getIngredients() != null) {
|
||||||
|
b.ingredients(recipe.getIngredients().stream()
|
||||||
|
.map(ingredient -> FullRecipeView.IngredientView.builder()
|
||||||
|
.amount(ingredient.getAmount())
|
||||||
|
.name(ingredient.getName())
|
||||||
|
.notes(ingredient.getNotes())
|
||||||
|
.build())
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (recipe.getMainImage() != null) {
|
if (recipe.getMainImage() != null) {
|
||||||
b.mainImage(this.imageToViewConverter.convert(recipe.getMainImage(), viewer, false));
|
b.mainImage(this.imageToViewConverter.convert(recipe.getMainImage(), viewer, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeRawText) {
|
if (includeRawText) {
|
||||||
b.rawText(recipe.getRawText());
|
b.rawText(recipe.getRawText());
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.build();
|
return b.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package app.mealsmadeeasy.api.recipe.converter;
|
||||||
|
|
||||||
|
import app.mealsmadeeasy.api.recipe.body.RecipeUpdateBody;
|
||||||
|
import app.mealsmadeeasy.api.recipe.spec.RecipeUpdateSpec;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class RecipeUpdateBodyToSpecConverter {
|
||||||
|
|
||||||
|
public RecipeUpdateSpec convert(RecipeUpdateBody body) {
|
||||||
|
final var b = RecipeUpdateSpec.builder()
|
||||||
|
.title(body.getTitle())
|
||||||
|
.preparationTime(body.getPreparationTime())
|
||||||
|
.cookingTime(body.getCookingTime())
|
||||||
|
.totalTime(body.getTotalTime())
|
||||||
|
.rawText(body.getRawText())
|
||||||
|
.isPublic(body.getIsPublic());
|
||||||
|
|
||||||
|
if (body.getIngredients() != null) {
|
||||||
|
b.ingredients(body.getIngredients().stream()
|
||||||
|
.map(ingredient -> RecipeUpdateSpec.IngredientUpdateSpec.builder()
|
||||||
|
.amount(ingredient.getAmount())
|
||||||
|
.name(ingredient.getName())
|
||||||
|
.notes(ingredient.getNotes())
|
||||||
|
.build())
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final @Nullable RecipeUpdateBody.MainImageUpdateBody mainImage = body.getMainImage();
|
||||||
|
if (mainImage != null) {
|
||||||
|
b.mainImage(
|
||||||
|
RecipeUpdateSpec.MainImageUpdateSpec.builder()
|
||||||
|
.username(mainImage.getUsername())
|
||||||
|
.filename(mainImage.getFilename())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,15 +5,27 @@ import lombok.Builder;
|
|||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
public class RecipeCreateSpec {
|
public class RecipeCreateSpec {
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public record IngredientCreateSpec(
|
||||||
|
@Nullable String amount,
|
||||||
|
String name,
|
||||||
|
@Nullable String notes
|
||||||
|
) {}
|
||||||
|
|
||||||
String slug;
|
String slug;
|
||||||
String title;
|
String title;
|
||||||
@Nullable Integer preparationTime;
|
@Nullable Integer preparationTime;
|
||||||
@Nullable Integer cookingTime;
|
@Nullable Integer cookingTime;
|
||||||
@Nullable Integer totalTime;
|
@Nullable Integer totalTime;
|
||||||
|
@Nullable List<IngredientCreateSpec> ingredients;
|
||||||
String rawText;
|
String rawText;
|
||||||
boolean isPublic;
|
boolean isPublic;
|
||||||
@Nullable Image mainImage;
|
@Nullable Image mainImage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,13 @@ 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 app.mealsmadeeasy.api.recipe.Recipe;
|
||||||
import app.mealsmadeeasy.api.recipe.body.RecipeUpdateBody;
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
// For now, we cannot change slug after creation.
|
// For now, we cannot change slug after creation.
|
||||||
// In the future, we may be able to have redirects from
|
// In the future, we may be able to have redirects from
|
||||||
// old slugs to new slugs.
|
// old slugs to new slugs.
|
||||||
@ -22,25 +23,8 @@ public class RecipeUpdateSpec {
|
|||||||
String filename;
|
String filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RecipeUpdateSpec from(RecipeUpdateBody body) {
|
@Builder
|
||||||
final var b = RecipeUpdateSpec.builder()
|
public record IngredientUpdateSpec(@Nullable String amount, String name, @Nullable String notes) {}
|
||||||
.title(body.getTitle())
|
|
||||||
.preparationTime(body.getPreparationTime())
|
|
||||||
.cookingTime(body.getCookingTime())
|
|
||||||
.totalTime(body.getTotalTime())
|
|
||||||
.rawText(body.getRawText())
|
|
||||||
.isPublic(body.getIsPublic());
|
|
||||||
final @Nullable RecipeUpdateBody.MainImageUpdateBody mainImage = body.getMainImage();
|
|
||||||
if (mainImage != null) {
|
|
||||||
b.mainImage(
|
|
||||||
MainImageUpdateSpec.builder()
|
|
||||||
.username(mainImage.getUsername())
|
|
||||||
.filename(mainImage.getFilename())
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return b.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For testing convenience only.
|
// For testing convenience only.
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@ -67,6 +51,7 @@ public class RecipeUpdateSpec {
|
|||||||
@Nullable Integer preparationTime;
|
@Nullable Integer preparationTime;
|
||||||
@Nullable Integer cookingTime;
|
@Nullable Integer cookingTime;
|
||||||
@Nullable Integer totalTime;
|
@Nullable Integer totalTime;
|
||||||
|
@Nullable List<IngredientUpdateSpec> ingredients;
|
||||||
String rawText;
|
String rawText;
|
||||||
Boolean isPublic;
|
Boolean isPublic;
|
||||||
@Nullable MainImageUpdateSpec mainImage;
|
@Nullable MainImageUpdateSpec mainImage;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package app.mealsmadeeasy.api.recipe.view;
|
package app.mealsmadeeasy.api.recipe.view;
|
||||||
|
|
||||||
import app.mealsmadeeasy.api.image.view.ImageView;
|
import app.mealsmadeeasy.api.image.view.ImageView;
|
||||||
import app.mealsmadeeasy.api.recipe.Recipe;
|
|
||||||
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
import app.mealsmadeeasy.api.user.view.UserInfoView;
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@ -9,39 +8,14 @@ import lombok.Value;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
public class FullRecipeView {
|
public class FullRecipeView {
|
||||||
|
|
||||||
public static FullRecipeView from(
|
@Builder
|
||||||
Recipe recipe,
|
public record IngredientView(@Nullable String amount, String name, @Nullable String notes) {}
|
||||||
String renderedText,
|
|
||||||
boolean includeRawText,
|
|
||||||
int starCount,
|
|
||||||
int viewerCount,
|
|
||||||
@Nullable ImageView mainImage
|
|
||||||
) {
|
|
||||||
final var b = FullRecipeView.builder()
|
|
||||||
.id(recipe.getId())
|
|
||||||
.created(recipe.getCreated())
|
|
||||||
.modified(recipe.getModified())
|
|
||||||
.slug(recipe.getSlug())
|
|
||||||
.title(recipe.getTitle())
|
|
||||||
.preparationTime(recipe.getPreparationTime())
|
|
||||||
.cookingTime(recipe.getCookingTime())
|
|
||||||
.totalTime(recipe.getTotalTime())
|
|
||||||
.text(renderedText)
|
|
||||||
.owner(UserInfoView.from(recipe.getOwner()))
|
|
||||||
.starCount(starCount)
|
|
||||||
.viewerCount(viewerCount)
|
|
||||||
.mainImage(mainImage)
|
|
||||||
.isPublic(recipe.getIsPublic());
|
|
||||||
if (includeRawText) {
|
|
||||||
b.rawText(recipe.getRawText());
|
|
||||||
}
|
|
||||||
return b.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer id;
|
Integer id;
|
||||||
OffsetDateTime created;
|
OffsetDateTime created;
|
||||||
@ -51,6 +25,7 @@ public class FullRecipeView {
|
|||||||
@Nullable Integer preparationTime;
|
@Nullable Integer preparationTime;
|
||||||
@Nullable Integer cookingTime;
|
@Nullable Integer cookingTime;
|
||||||
@Nullable Integer totalTime;
|
@Nullable Integer totalTime;
|
||||||
|
@Nullable List<IngredientView> ingredients;
|
||||||
String text;
|
String text;
|
||||||
@Nullable String rawText;
|
@Nullable String rawText;
|
||||||
UserInfoView owner;
|
UserInfoView owner;
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE recipe ADD COLUMN ingredients JSONB;
|
||||||
Loading…
Reference in New Issue
Block a user