feat(backend): add heroImageUrl and tags to RecipeSummaryResponse

GET /v1/recipes was returning RecipeSummaryResponse with no tags and
only heroImagePreview. The planner frontend needs protein tags to pick
gradient backgrounds for tiles without a hero image.

- Replace JPQL constructor projection with entity query + LEFT JOIN FETCH tags
- Map Recipe entity to RecipeSummaryResponse in service (includes tags + heroImageUrl)
- Drop heroImagePreview in favour of heroImageUrl on the summary DTO

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 12:03:03 +02:00
parent ed4cdbf230
commit d901310897
4 changed files with 20 additions and 8 deletions

View File

@@ -1,6 +1,5 @@
package com.recipeapp.recipe;
import com.recipeapp.recipe.dto.RecipeSummaryResponse;
import com.recipeapp.recipe.entity.Recipe;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
@@ -17,9 +16,8 @@ public interface RecipeRepository extends JpaRepository<Recipe, UUID> {
List<Recipe> findByHouseholdIdAndDeletedAtIsNull(UUID householdId);
@Query("""
SELECT new com.recipeapp.recipe.dto.RecipeSummaryResponse(
r.id, r.name, r.serves, r.cookTimeMin, r.effort, r.heroImagePreview)
FROM Recipe r
SELECT r FROM Recipe r
LEFT JOIN FETCH r.tags
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), '%')))
@@ -27,7 +25,7 @@ public interface RecipeRepository extends JpaRepository<Recipe, UUID> {
AND (:cookTimeMaxMin IS NULL OR r.cookTimeMin <= :cookTimeMaxMin)
ORDER BY r.createdAt DESC
""")
List<RecipeSummaryResponse> findFiltered(
List<Recipe> findFiltered(
@Param("householdId") UUID householdId,
@Param("search") String search,
@Param("effort") String effort,

View File

@@ -42,7 +42,15 @@ public class RecipeService {
@Transactional(readOnly = true)
public List<RecipeSummaryResponse> listRecipes(UUID householdId, String search, String effort,
Integer cookTimeMaxMin, String sort, int limit, int offset) {
return recipeRepository.findFiltered(householdId, search, effort, cookTimeMaxMin, sort, limit, offset);
return recipeRepository.findFiltered(householdId, search, effort, cookTimeMaxMin, sort, limit, offset)
.stream()
.map(r -> new RecipeSummaryResponse(
r.getId(), r.getName(), r.getServes(), r.getCookTimeMin(), r.getEffort(),
r.getHeroImageUrl(),
r.getTags().stream()
.map(t -> new TagResponse(t.getId(), t.getName(), t.getTagType()))
.toList()))
.toList();
}
@Transactional(readOnly = true)

View File

@@ -1,5 +1,6 @@
package com.recipeapp.recipe.dto;
import java.util.List;
import java.util.UUID;
public record RecipeSummaryResponse(
@@ -8,5 +9,6 @@ public record RecipeSummaryResponse(
short serves,
short cookTimeMin,
String effort,
String heroImagePreview
String heroImageUrl,
List<TagResponse> tags
) {}