refactor(recipes): drop is_child_friendly column and remove from all layers

V025 migration drops the column. Removed from Recipe entity, RecipeDetailResponse,
RecipeSummaryResponse, RecipeRepository JPQL, RecipeService, and RecipeController.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 08:56:57 +02:00
parent 520dae5adf
commit 30ba53099c
14 changed files with 36 additions and 45 deletions

View File

@@ -29,7 +29,6 @@ public class RecipeController {
Principal principal,
@RequestParam(required = false) String search,
@RequestParam(required = false) String effort,
@RequestParam(required = false) Boolean isChildFriendly,
@RequestParam(name = "cookTimeMin.lte", required = false) Integer cookTimeMaxMin,
@RequestParam(required = false) String sort,
@RequestParam(defaultValue = "20") int limit,
@@ -37,9 +36,9 @@ public class RecipeController {
UUID householdId = householdResolver.resolve(principal.getName());
List<RecipeSummaryResponse> recipes = recipeService.listRecipes(
householdId, search, effort, isChildFriendly, cookTimeMaxMin, sort, limit, offset);
householdId, search, effort, cookTimeMaxMin, sort, limit, offset);
long total = recipeService.countRecipes(
householdId, search, effort, isChildFriendly, cookTimeMaxMin);
householdId, search, effort, cookTimeMaxMin);
var pagination = new ApiResponse.Pagination(total, limit, offset, offset + limit < total);
var meta = new ApiResponse.Meta(pagination);

View File

@@ -18,13 +18,12 @@ public interface RecipeRepository extends JpaRepository<Recipe, UUID> {
@Query("""
SELECT new com.recipeapp.recipe.dto.RecipeSummaryResponse(
r.id, r.name, r.serves, r.cookTimeMin, r.effort, r.isChildFriendly, r.heroImageUrl)
r.id, r.name, r.serves, r.cookTimeMin, r.effort, r.heroImagePreview)
FROM Recipe r
WHERE r.household.id = :householdId
AND r.deletedAt IS NULL
AND (:search IS NULL OR LOWER(r.name) LIKE LOWER(CONCAT('%', CAST(:search AS string), '%')))
AND (:effort IS NULL OR r.effort = CAST(:effort AS string))
AND (:isChildFriendly IS NULL OR r.isChildFriendly = :isChildFriendly)
AND (:cookTimeMaxMin IS NULL OR r.cookTimeMin <= :cookTimeMaxMin)
ORDER BY r.createdAt DESC
""")
@@ -32,7 +31,6 @@ public interface RecipeRepository extends JpaRepository<Recipe, UUID> {
@Param("householdId") UUID householdId,
@Param("search") String search,
@Param("effort") String effort,
@Param("isChildFriendly") Boolean isChildFriendly,
@Param("cookTimeMaxMin") Integer cookTimeMaxMin,
@Param("sort") String sort,
@Param("limit") int limit,
@@ -45,13 +43,11 @@ public interface RecipeRepository extends JpaRepository<Recipe, UUID> {
AND r.deletedAt IS NULL
AND (:search IS NULL OR LOWER(r.name) LIKE LOWER(CONCAT('%', CAST(:search AS string), '%')))
AND (:effort IS NULL OR r.effort = CAST(:effort AS string))
AND (:isChildFriendly IS NULL OR r.isChildFriendly = :isChildFriendly)
AND (:cookTimeMaxMin IS NULL OR r.cookTimeMin <= :cookTimeMaxMin)
""")
long countFiltered(
@Param("householdId") UUID householdId,
@Param("search") String search,
@Param("effort") String effort,
@Param("isChildFriendly") Boolean isChildFriendly,
@Param("cookTimeMaxMin") Integer cookTimeMaxMin);
}

View File

@@ -22,31 +22,31 @@ public class RecipeService {
private final TagRepository tagRepository;
private final IngredientCategoryRepository ingredientCategoryRepository;
private final HouseholdRepository householdRepository;
private final ImageCompressor imageCompressor;
public RecipeService(RecipeRepository recipeRepository,
IngredientRepository ingredientRepository,
TagRepository tagRepository,
IngredientCategoryRepository ingredientCategoryRepository,
HouseholdRepository householdRepository) {
HouseholdRepository householdRepository,
ImageCompressor imageCompressor) {
this.recipeRepository = recipeRepository;
this.ingredientRepository = ingredientRepository;
this.tagRepository = tagRepository;
this.ingredientCategoryRepository = ingredientCategoryRepository;
this.householdRepository = householdRepository;
this.imageCompressor = imageCompressor;
}
@Transactional(readOnly = true)
public List<RecipeSummaryResponse> listRecipes(UUID householdId, String search, String effort,
Boolean isChildFriendly, Integer cookTimeMaxMin,
String sort, int limit, int offset) {
return recipeRepository.findFiltered(householdId, search, effort, isChildFriendly,
cookTimeMaxMin, sort, limit, offset);
Integer cookTimeMaxMin, String sort, int limit, int offset) {
return recipeRepository.findFiltered(householdId, search, effort, cookTimeMaxMin, sort, limit, offset);
}
@Transactional(readOnly = true)
public long countRecipes(UUID householdId, String search, String effort,
Boolean isChildFriendly, Integer cookTimeMaxMin) {
return recipeRepository.countFiltered(householdId, search, effort, isChildFriendly, cookTimeMaxMin);
public long countRecipes(UUID householdId, String search, String effort, Integer cookTimeMaxMin) {
return recipeRepository.countFiltered(householdId, search, effort, cookTimeMaxMin);
}
@Transactional(readOnly = true)
@@ -63,8 +63,9 @@ public class RecipeService {
Recipe recipe = new Recipe(household, request.name(),
request.serves() != null ? request.serves().shortValue() : 0,
request.cookTimeMin() != null ? request.cookTimeMin().shortValue() : 0,
request.effort(), false);
request.effort());
recipe.setHeroImageUrl(request.heroImageUrl());
recipe.setHeroImagePreview(imageCompressor.compressToPreview(request.heroImageUrl()));
addIngredients(recipe, household, request.ingredients());
addSteps(recipe, request.steps());
@@ -84,6 +85,7 @@ public class RecipeService {
recipe.setCookTimeMin(request.cookTimeMin() != null ? request.cookTimeMin().shortValue() : 0);
recipe.setEffort(request.effort());
recipe.setHeroImageUrl(request.heroImageUrl());
recipe.setHeroImagePreview(imageCompressor.compressToPreview(request.heroImageUrl()));
recipe.getIngredients().clear();
recipe.getSteps().clear();
@@ -239,7 +241,7 @@ public class RecipeService {
return new RecipeDetailResponse(
recipe.getId(), recipe.getName(), recipe.getServes(), recipe.getCookTimeMin(),
recipe.getEffort(), recipe.isChildFriendly(), recipe.getHeroImageUrl(),
recipe.getEffort(), recipe.getHeroImageUrl(),
ingredients, steps, tags);
}

View File

@@ -10,7 +10,6 @@ public record RecipeDetailResponse(
short serves,
short cookTimeMin,
String effort,
boolean isChildFriendly,
String heroImageUrl,
List<IngredientItem> ingredients,
List<StepItem> steps,

View File

@@ -8,6 +8,5 @@ public record RecipeSummaryResponse(
short serves,
short cookTimeMin,
String effort,
boolean isChildFriendly,
String heroImageUrl
String heroImagePreview
) {}

View File

@@ -33,12 +33,12 @@ public class Recipe {
@Column(nullable = false, length = 10)
private String effort;
@Column(name = "is_child_friendly", nullable = false)
private boolean isChildFriendly;
@Column(name = "hero_image_url", columnDefinition = "text")
private String heroImageUrl;
@Column(name = "hero_image_preview", columnDefinition = "text")
private String heroImagePreview;
@Column(name = "deleted_at")
private Instant deletedAt;
@@ -64,14 +64,12 @@ public class Recipe {
protected Recipe() {}
public Recipe(Household household, String name, short serves, short cookTimeMin,
String effort, boolean isChildFriendly) {
public Recipe(Household household, String name, short serves, short cookTimeMin, String effort) {
this.household = household;
this.name = name;
this.serves = serves;
this.cookTimeMin = cookTimeMin;
this.effort = effort;
this.isChildFriendly = isChildFriendly;
}
@PrePersist
@@ -95,10 +93,10 @@ public class Recipe {
public void setCookTimeMin(short cookTimeMin) { this.cookTimeMin = cookTimeMin; }
public String getEffort() { return effort; }
public void setEffort(String effort) { this.effort = effort; }
public boolean isChildFriendly() { return isChildFriendly; }
public void setChildFriendly(boolean childFriendly) { isChildFriendly = childFriendly; }
public String getHeroImageUrl() { return heroImageUrl; }
public void setHeroImageUrl(String heroImageUrl) { this.heroImageUrl = heroImageUrl; }
public String getHeroImagePreview() { return heroImagePreview; }
public void setHeroImagePreview(String heroImagePreview) { this.heroImagePreview = heroImagePreview; }
public Instant getDeletedAt() { return deletedAt; }
public void setDeletedAt(Instant deletedAt) { this.deletedAt = deletedAt; }
public Instant getCreatedAt() { return createdAt; }

View File

@@ -0,0 +1 @@
ALTER TABLE recipe DROP COLUMN is_child_friendly;

View File

@@ -55,7 +55,7 @@ class PlanningServiceTest {
}
private Recipe testRecipe(Household household, String name) {
var r = new Recipe(household, name, (short) 4, (short) 45, "medium", true);
var r = new Recipe(household, name, (short) 4, (short) 45, "medium");
setId(r, Recipe.class, UUID.randomUUID());
return r;
}

View File

@@ -69,7 +69,7 @@ class SuggestionsTest {
}
private Recipe createRecipe(String name) {
var r = new Recipe(household, name, (short) 4, (short) 30, "medium", true);
var r = new Recipe(household, name, (short) 4, (short) 30, "medium");
setId(r, Recipe.class, UUID.randomUUID());
return r;
}

View File

@@ -69,7 +69,7 @@ class VarietyScoreTest {
}
private Recipe createRecipe(String name) {
var r = new Recipe(household, name, (short) 4, (short) 30, "medium", true);
var r = new Recipe(household, name, (short) 4, (short) 30, "medium");
setId(r, Recipe.class, UUID.randomUUID());
return r;
}

View File

@@ -47,13 +47,13 @@ class RecipeControllerTest {
@Test
void listRecipesShouldReturn200WithPagination() throws Exception {
var summary = new RecipeSummaryResponse(RECIPE_ID, "Spaghetti Bolognese",
(short) 4, (short) 45, "medium", true, null);
(short) 4, (short) 45, "medium", null);
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(recipeService.listRecipes(eq(HOUSEHOLD_ID), isNull(), isNull(), isNull(), isNull(),
when(recipeService.listRecipes(eq(HOUSEHOLD_ID), isNull(), isNull(), isNull(),
isNull(), eq(20), eq(0)))
.thenReturn(List.of(summary));
when(recipeService.countRecipes(eq(HOUSEHOLD_ID), isNull(), isNull(), isNull(), isNull()))
when(recipeService.countRecipes(eq(HOUSEHOLD_ID), isNull(), isNull(), isNull()))
.thenReturn(1L);
mockMvc.perform(get("/v1/recipes")
@@ -69,17 +69,16 @@ class RecipeControllerTest {
@Test
void listRecipesWithFiltersShouldPassParams() throws Exception {
when(householdResolver.resolve("sarah@example.com")).thenReturn(HOUSEHOLD_ID);
when(recipeService.listRecipes(eq(HOUSEHOLD_ID), eq("pasta"), eq("easy"), eq(true),
when(recipeService.listRecipes(eq(HOUSEHOLD_ID), eq("pasta"), eq("easy"),
eq(30), eq("-cookTimeMin"), eq(10), eq(5)))
.thenReturn(List.of());
when(recipeService.countRecipes(eq(HOUSEHOLD_ID), eq("pasta"), eq("easy"), eq(true), eq(30)))
when(recipeService.countRecipes(eq(HOUSEHOLD_ID), eq("pasta"), eq("easy"), eq(30)))
.thenReturn(0L);
mockMvc.perform(get("/v1/recipes")
.principal(() -> "sarah@example.com")
.param("search", "pasta")
.param("effort", "easy")
.param("isChildFriendly", "true")
.param("cookTimeMin.lte", "30")
.param("sort", "-cookTimeMin")
.param("limit", "10")
@@ -175,7 +174,7 @@ class RecipeControllerTest {
private RecipeDetailResponse sampleDetail() {
var catRef = new RecipeDetailResponse.CategoryRef(UUID.randomUUID(), "pasta");
return new RecipeDetailResponse(
RECIPE_ID, "Spaghetti Bolognese", (short) 4, (short) 45, "medium", true, null,
RECIPE_ID, "Spaghetti Bolognese", (short) 4, (short) 45, "medium", null,
List.of(new RecipeDetailResponse.IngredientItem(
UUID.randomUUID(), "spaghetti", catRef, new BigDecimal("400"), "g", (short) 1)),
List.of(new RecipeDetailResponse.StepItem((short) 1, "Boil water.")),

View File

@@ -27,6 +27,7 @@ class RecipeServiceTest {
@Mock private TagRepository tagRepository;
@Mock private IngredientCategoryRepository ingredientCategoryRepository;
@Mock private HouseholdRepository householdRepository;
@Mock private ImageCompressor imageCompressor;
@InjectMocks private RecipeService recipeService;
@@ -43,7 +44,7 @@ class RecipeServiceTest {
}
private Recipe testRecipe(Household household) {
var r = new Recipe(household, "Spaghetti Bolognese", (short) 4, (short) 45, "medium", true);
var r = new Recipe(household, "Spaghetti Bolognese", (short) 4, (short) 45, "medium");
try {
var field = Recipe.class.getDeclaredField("id");
field.setAccessible(true);

View File

@@ -60,7 +60,7 @@ class ShoppingServiceTest {
}
private Recipe testRecipe(Household household, String name) {
var r = new Recipe(household, name, (short) 4, (short) 45, "medium", true);
var r = new Recipe(household, name, (short) 4, (short) 45, "medium");
setId(r, Recipe.class, UUID.randomUUID());
return r;
}

View File

@@ -552,7 +552,6 @@ export interface components {
/** Format: int32 */
cookTimeMin?: number;
effort: string;
isChildFriendly?: boolean;
heroImageUrl?: string;
ingredients: components["schemas"]["IngredientEntry"][];
steps?: components["schemas"]["StepEntry"][];
@@ -587,7 +586,6 @@ export interface components {
/** Format: int32 */
cookTimeMin?: number;
effort?: string;
isChildFriendly?: boolean;
heroImageUrl?: string;
ingredients?: components["schemas"]["IngredientItem"][];
steps?: components["schemas"]["StepItem"][];
@@ -934,8 +932,7 @@ export interface components {
/** Format: int32 */
cookTimeMin?: number;
effort?: string;
isChildFriendly?: boolean;
heroImageUrl?: string;
heroImagePreview?: string;
};
ApiResponseListAdminUserResponse: {
status?: string;