using Microsoft.EntityFrameworkCore; using MealPlanner.Data; using MealPlanner.Models; namespace MealPlanner.Services; public class RecipeService(AppDbContext db, TheMealDbClient mealDbClient) { public async Task> GetOwnRecipesAsync(string userId) { return await db.Recipes .Include(r => r.Ingredients) .Where(r => r.UserId == userId) .OrderByDescending(r => r.CreatedAt) .ToListAsync(); } public async Task GetByIdAsync(Guid id) { return await db.Recipes .Include(r => r.Ingredients) .FirstOrDefaultAsync(r => r.Id == id); } public async Task GetByIdOrFetchAsync(Guid id) { var recipe = await GetByIdAsync(id); return recipe; } public async Task> SearchAsync(string query, string userId) { // Local own recipes var own = await db.Recipes .Include(r => r.Ingredients) .Where(r => r.UserId == userId && r.Title.ToLower().Contains(query.ToLower())) .ToListAsync(); // External results var external = await mealDbClient.SearchAsync(query); // Deduplicate external by ExternalId var existingExternalIds = await db.Recipes .Where(r => r.Source == RecipeSource.TheMealDb && r.ExternalId != null) .Select(r => r.ExternalId!) .ToListAsync(); var newExternal = external .Where(r => r.ExternalId != null && !existingExternalIds.Contains(r.ExternalId)) .ToList(); return [.. own, .. newExternal]; } public async Task CreateAsync(string userId, Recipe recipe) { recipe.Id = Guid.NewGuid(); recipe.UserId = userId; recipe.Source = RecipeSource.Own; recipe.CreatedAt = DateTime.UtcNow; foreach (var ing in recipe.Ingredients) { ing.Id = Guid.NewGuid(); ing.RecipeId = recipe.Id; } db.Recipes.Add(recipe); await db.SaveChangesAsync(); return recipe; } public async Task UpdateAsync(Guid id, string userId, Recipe updated) { var recipe = await db.Recipes .Include(r => r.Ingredients) .FirstOrDefaultAsync(r => r.Id == id); if (recipe is null) return null; if (recipe.UserId != userId) throw new UnauthorizedAccessException(); recipe.Title = updated.Title; recipe.Instructions = updated.Instructions; recipe.ImageUrl = updated.ImageUrl; // Replace ingredients db.RecipeIngredients.RemoveRange(recipe.Ingredients); recipe.Ingredients = updated.Ingredients.Select(ing => new RecipeIngredient { Id = Guid.NewGuid(), RecipeId = recipe.Id, Name = ing.Name, Amount = ing.Amount, Unit = ing.Unit, Category = ing.Category, }).ToList(); await db.SaveChangesAsync(); return recipe; } public async Task DeleteAsync(Guid id, string userId) { var recipe = await db.Recipes.FirstOrDefaultAsync(r => r.Id == id); if (recipe is null) return false; if (recipe.UserId != userId) throw new UnauthorizedAccessException(); db.Recipes.Remove(recipe); await db.SaveChangesAsync(); return true; } // Save an external recipe to DB so it can be referenced in meal plans public async Task EnsurePersistedAsync(Recipe recipe) { if (recipe.ExternalId is not null) { var existing = await db.Recipes .Include(r => r.Ingredients) .FirstOrDefaultAsync(r => r.ExternalId == recipe.ExternalId); if (existing is not null) return existing; } db.Recipes.Add(recipe); await db.SaveChangesAsync(); return recipe; } }