Merge develop into feature/Show-ShoppingList-Of-Active-RecipeCard

This commit is contained in:
Francesco Lorenzo D'Amico
2025-06-04 10:59:20 +02:00
24 changed files with 1419 additions and 140 deletions
@@ -47,6 +47,7 @@
return await _context.Categories
.Include(c => c.Recipes)
.ThenInclude(r => r.MediaFiles)
.AsSplitQuery()
.ToListAsync();
}
}
@@ -16,8 +16,10 @@
public async Task<IEnumerable<Recipe>> GetFavoriteRecipesAsync()
{
return await _context.Recipes
.Where(r => r.IsFavorite)
.ToListAsync();
.Where(r => r.IsFavorite)
.Include(r => r.Favorite)
.Include(r => r.MediaFiles)
.ToListAsync();
}
public async Task<bool> IsFavoriteAsync(Guid recipeId)
@@ -28,12 +30,32 @@
public async Task AddFavoriteAsync(Guid recipeId)
{
var recipe = await _context.Recipes.FindAsync(recipeId);
if (recipe != null && !recipe.IsFavorite)
var recipe = await _context.Recipes
.Include(r => r.Favorite)
.FirstOrDefaultAsync(r => r.Id == recipeId);
if (recipe == null)
{
recipe.IsFavorite = true;
await _context.SaveChangesAsync();
throw new InvalidOperationException("Recipe not found.");
}
if (recipe.IsFavorite)
{
throw new InvalidOperationException("Recipe is already a favorite.");
}
recipe.IsFavorite = true;
if (recipe.Favorite == null || recipe.Favorite.Id == Guid.Empty)
{
recipe.Favorite = new Models.BackendModels.Favorit.Favorit
{
Id = Guid.NewGuid(),
CreatedAt = DateTime.Now,
};
}
await _context.SaveChangesAsync();
}
public async Task RemoveFavoriteAsync(Guid recipeId)
@@ -1,5 +1,6 @@
namespace Francesco.Recipes.World.Repositories.MediaFile
{
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models.BackendModels.MediaFile;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
@@ -83,7 +84,7 @@
{
var recipe = await _recipeRepository.GetRecipeAsync(recipeId);
var isImage = mediaFile.ContentType.StartsWith("image/");
var isImage = mediaFile.ContentType.StartsWith(ContentType.Image);
var isVideo = mediaFile.ContentType.StartsWith("video/");
if (!isImage && !isVideo)
@@ -93,7 +94,7 @@
if (isImage)
{
await RemoveExistingMediaAsync(recipe, "image/");
await RemoveExistingMediaAsync(recipe, ContentType.Image);
}
else
{
@@ -1,5 +1,6 @@
namespace Francesco.Recipes.World.Repositories.Recipe
{
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
@@ -17,6 +18,7 @@
Task<Recipe> CreateRecipeForCategoryAsync(Category category, string name, string description, Difficulty difficulty, int servings, TimeSpan preparationTime, TimeSpan cookingTime);
Task<IEnumerable<Recipe>> SearchInRecipesAndIngredients(string searchterm);
Task<bool> DeleteRecipeAsync(Guid recipeId);
Task<IEnumerable<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm);
}
}
@@ -1,6 +1,9 @@
namespace Francesco.Recipes.World.Repositories.Recipe
{
using System.Linq.Expressions;
using Francesco.Recipes.World.Constants;
using Francesco.Recipes.World.Data;
using Francesco.Recipes.World.Models;
using Francesco.Recipes.World.Models.BackendModels.Category;
using Francesco.Recipes.World.Models.BackendModels.Ingredient;
using Francesco.Recipes.World.Models.BackendModels.Recipe;
@@ -38,6 +41,7 @@
.ThenInclude(ri => ri.Unit)
.Include(r => r.MediaFiles)
.Include(r => r.Instructions)
.ThenInclude(i => i.MediaFiles)
.FirstOrDefaultAsync(r => r.Id == recipeId);
}
@@ -147,21 +151,32 @@
await _context.SaveChangesAsync();
}
public async Task<IEnumerable<Recipe>> SearchInRecipesAndIngredients(string searchTerm)
public async Task<IEnumerable<SearchViewModel>> SearchInRecipesAndIngredients(string searchTerm)
{
var queryable = _context.Recipes
.Include(r => r.RecipeIngredients)
.ThenInclude(ri => ri.Ingredient)
.AsQueryable();
if (!string.IsNullOrWhiteSpace(searchTerm))
try
{
searchTerm = searchTerm.ToLower();
queryable = queryable.Where(r => r.Name.ToLower().Contains(searchTerm) ||
r.RecipeIngredients.Any(ri => ri.Ingredient.Name.ToLower().Contains(searchTerm)));
}
if (string.IsNullOrWhiteSpace(searchTerm))
{
return await _context.Recipes
.OrderByDescending(r => r.CreatedAt)
.Take(20)
.Select(SearchViewModelSelector())
.ToListAsync();
}
return await queryable.ToListAsync();
var normalizedSearchTerm = searchTerm.ToLower();
return await ApplyRecipeSearchFilter(_context.Recipes, normalizedSearchTerm)
.OrderByDescending(r => r.CreatedAt)
.Take(100)
.Select(SearchViewModelSelector())
.ToListAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Error in SearchInRecipesAndIngredientsOptimized: {ex.Message}");
return new List<SearchViewModel>();
}
}
public async Task<IReadOnlyCollection<Recipe>> GetRecipesByDifficultyAsync(Difficulty? difficulty)
@@ -182,5 +197,77 @@
.ThenInclude(ri => ri.Ingredient)
.ToListAsync();
}
private static Expression<Func<Recipe, SearchViewModel>> SearchViewModelSelector()
{
return r => new SearchViewModel
{
Id = r.Id,
Name = r.Name,
Description = r.Description,
IsFavorite = r.IsFavorite,
ImageData = r.MediaFiles
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
.Select(m => m.Data)
.FirstOrDefault(),
MimeType = r.MediaFiles
.Where(m => m.MimeType != null && m.MimeType.StartsWith(ContentType.Image))
.Select(m => m.MimeType)
.FirstOrDefault(),
Ingredients = r.RecipeIngredients.Select(ri => ri.Ingredient.Name).ToList(),
TotalTime = r.PreparationTime.Add(r.CookingTime),
};
}
private static IQueryable<Recipe> ApplyRecipeSearchFilter(IQueryable<Recipe> query, string normalizedSearchTerm)
{
return query.Where(r =>
EF.Functions.Like(r.Name.ToLower(), $"%{normalizedSearchTerm}%") ||
(r.Description != null && EF.Functions.Like(r.Description.ToLower(), $"%{normalizedSearchTerm}%")) ||
r.RecipeIngredients.Any(ri => EF.Functions.Like(ri.Ingredient.Name.ToLower(), $"%{normalizedSearchTerm}%")));
}
public async Task<bool> DeleteRecipeAsync(Guid recipeId)
{
var recipe = await GetRecipeByIdAsync(recipeId);
if (recipe == null)
{
return false;
}
if (recipe.RecipeIngredients != null && recipe.RecipeIngredients.Any())
{
_context.RecipeIngredients.RemoveRange(recipe.RecipeIngredients);
}
if (recipe.Instructions != null && recipe.Instructions.Any())
{
foreach (var instruction in recipe.Instructions)
{
if (instruction.MediaFiles != null && instruction.MediaFiles.Any())
{
_context.MediaFiles.RemoveRange(instruction.MediaFiles);
}
}
_context.Instructions.RemoveRange(recipe.Instructions);
}
if (recipe.MediaFiles != null && recipe.MediaFiles.Any())
{
_context.MediaFiles.RemoveRange(recipe.MediaFiles);
}
if (recipe.Favorit != null && recipe.Favorit.Id != Guid.Empty)
{
_context.Remove(recipe.Favorit);
}
_context.Recipes.Remove(recipe);
await _context.SaveChangesAsync();
return true;
}
}
}