fix: enable nested base recipe references to display correctly
All checks were successful
CI / update (push) Successful in 1m10s

- Add recursive population for nested base recipe references (up to 3 levels deep) in API endpoints
- Implement recursive mapping of baseRecipeRef to resolvedRecipe for all nesting levels
- Add recursive flattening functions in frontend components to handle nested references
- Fix TranslationApproval to use short_name instead of ObjectId for base recipe lookups
- Add circular reference detection to prevent infinite loops

This ensures that when Recipe A references Recipe B as a base, and Recipe B references Recipe C, all three recipes' content is properly displayed.
This commit is contained in:
2026-01-04 23:41:47 +01:00
parent 8a152c5fb2
commit 8eacf1f5d0
5 changed files with 180 additions and 87 deletions

View File

@@ -17,11 +17,27 @@ export const GET: RequestHandler = async ({ params }) => {
})
.populate({
path: 'translations.en.ingredients.baseRecipeRef',
select: 'short_name name ingredients instructions translations'
select: 'short_name name ingredients instructions translations',
populate: {
path: 'ingredients.baseRecipeRef',
select: 'short_name name ingredients instructions translations',
populate: {
path: 'ingredients.baseRecipeRef',
select: 'short_name name ingredients instructions translations'
}
}
})
.populate({
path: 'translations.en.instructions.baseRecipeRef',
select: 'short_name name ingredients instructions translations'
select: 'short_name name ingredients instructions translations',
populate: {
path: 'instructions.baseRecipeRef',
select: 'short_name name ingredients instructions translations',
populate: {
path: 'instructions.baseRecipeRef',
select: 'short_name name ingredients instructions translations'
}
}
})
.lean();
@@ -64,23 +80,32 @@ export const GET: RequestHandler = async ({ params }) => {
germanShortName: recipe.short_name,
};
// Map populated base recipe refs to resolvedRecipe field
if (englishRecipe.ingredients) {
englishRecipe.ingredients = englishRecipe.ingredients.map((item: any) => {
// Recursively map populated base recipe refs to resolvedRecipe field
function mapBaseRecipeRefs(items: any[]): any[] {
return items.map((item: any) => {
if (item.type === 'reference' && item.baseRecipeRef) {
return { ...item, resolvedRecipe: item.baseRecipeRef };
const resolvedRecipe = { ...item.baseRecipeRef };
// Recursively map nested baseRecipeRefs
if (resolvedRecipe.ingredients) {
resolvedRecipe.ingredients = mapBaseRecipeRefs(resolvedRecipe.ingredients);
}
if (resolvedRecipe.instructions) {
resolvedRecipe.instructions = mapBaseRecipeRefs(resolvedRecipe.instructions);
}
return { ...item, resolvedRecipe };
}
return item;
});
}
if (englishRecipe.ingredients) {
englishRecipe.ingredients = mapBaseRecipeRefs(englishRecipe.ingredients);
}
if (englishRecipe.instructions) {
englishRecipe.instructions = englishRecipe.instructions.map((item: any) => {
if (item.type === 'reference' && item.baseRecipeRef) {
return { ...item, resolvedRecipe: item.baseRecipeRef };
}
return item;
});
englishRecipe.instructions = mapBaseRecipeRefs(englishRecipe.instructions);
}
// Merge English alt/caption with original image paths

View File

@@ -9,11 +9,27 @@ export const GET: RequestHandler = async ({params}) => {
let recipe = await Recipe.findOne({ short_name: params.name})
.populate({
path: 'ingredients.baseRecipeRef',
select: 'short_name name ingredients translations'
select: 'short_name name ingredients translations',
populate: {
path: 'ingredients.baseRecipeRef',
select: 'short_name name ingredients translations',
populate: {
path: 'ingredients.baseRecipeRef',
select: 'short_name name ingredients translations'
}
}
})
.populate({
path: 'instructions.baseRecipeRef',
select: 'short_name name instructions translations'
select: 'short_name name instructions translations',
populate: {
path: 'instructions.baseRecipeRef',
select: 'short_name name instructions translations',
populate: {
path: 'instructions.baseRecipeRef',
select: 'short_name name instructions translations'
}
}
})
.lean() as RecipeModelType[];
@@ -22,23 +38,32 @@ export const GET: RequestHandler = async ({params}) => {
throw error(404, "Recipe not found")
}
// Map populated refs to resolvedRecipe field
if (recipe?.ingredients) {
recipe.ingredients = recipe.ingredients.map((item: any) => {
// Recursively map populated refs to resolvedRecipe field
function mapBaseRecipeRefs(items: any[]): any[] {
return items.map((item: any) => {
if (item.type === 'reference' && item.baseRecipeRef) {
return { ...item, resolvedRecipe: item.baseRecipeRef };
const resolvedRecipe = { ...item.baseRecipeRef };
// Recursively map nested baseRecipeRefs
if (resolvedRecipe.ingredients) {
resolvedRecipe.ingredients = mapBaseRecipeRefs(resolvedRecipe.ingredients);
}
if (resolvedRecipe.instructions) {
resolvedRecipe.instructions = mapBaseRecipeRefs(resolvedRecipe.instructions);
}
return { ...item, resolvedRecipe };
}
return item;
});
}
if (recipe?.ingredients) {
recipe.ingredients = mapBaseRecipeRefs(recipe.ingredients);
}
if (recipe?.instructions) {
recipe.instructions = recipe.instructions.map((item: any) => {
if (item.type === 'reference' && item.baseRecipeRef) {
return { ...item, resolvedRecipe: item.baseRecipeRef };
}
return item;
});
recipe.instructions = mapBaseRecipeRefs(recipe.instructions);
}
return json(recipe);