| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using Recepie.Data;
- using Recepie.Models;
- using Recepie.ViewModels;
- namespace Recepie.Controllers
- {
- public class WeekPlannerController : Controller
- {
- private readonly RecipeContext _context;
- public WeekPlannerController(RecipeContext context)
- {
- _context = context;
- }
- public async Task<IActionResult> Index(DateTime? startDate = null)
- {
- var weekStart = startDate ?? DateTime.Today.AddDays(-(int)DateTime.Today.DayOfWeek + 1); // Monday
- var currentWeekPlan = await _context.WeekPlans
- .Include(wp => wp.DayPlans.OrderBy(dp => dp.Date))
- .ThenInclude(dp => dp.Recipe)
- .FirstOrDefaultAsync(wp => wp.StartDate.Date == weekStart.Date);
- var mainIngredients = await GetMainIngredientsAsync();
- var viewModel = new WeekPlannerViewModel
- {
- CurrentWeekPlan = currentWeekPlan,
- AvailableMainIngredients = mainIngredients,
- StartDate = weekStart
- };
- return View(viewModel);
- }
- [HttpPost]
- public async Task<IActionResult> CreateWeekPlan(DateTime startDate, string planName = "")
- {
- if (string.IsNullOrEmpty(planName))
- planName = $"Week Plan {startDate:MMM dd, yyyy}";
- var weekPlan = new WeekPlan
- {
- Name = planName,
- StartDate = startDate,
- CreatedAt = DateTime.Now
- };
- _context.WeekPlans.Add(weekPlan);
- await _context.SaveChangesAsync();
- // Create day plans for the week
- for (int i = 0; i < 7; i++)
- {
- var dayPlan = new DayPlan
- {
- WeekPlanId = weekPlan.Id,
- DayOfWeek = (DayOfWeek)((int)(startDate.DayOfWeek + i) % 7),
- Date = startDate.AddDays(i)
- };
- _context.DayPlans.Add(dayPlan);
- }
- await _context.SaveChangesAsync();
- return RedirectToAction(nameof(Index));
- }
- public async Task<IActionResult> ConfigureDay(int weekPlanId, DayOfWeek dayOfWeek)
- {
- var weekPlan = await _context.WeekPlans
- .Include(wp => wp.DayPlans)
- .FirstOrDefaultAsync(wp => wp.Id == weekPlanId);
- if (weekPlan == null)
- return NotFound();
- var dayPlan = weekPlan.DayPlans.FirstOrDefault(dp => dp.DayOfWeek == dayOfWeek);
- if (dayPlan == null)
- return NotFound();
- var mainIngredients = await GetMainIngredientsAsync();
- var suggestedRecipes = await GetSuggestedRecipesAsync(dayPlan.MainIngredient, dayPlan.BannedIngredients, dayPlan.RequiredIngredients);
- var viewModel = new DayPlanConfigViewModel
- {
- DayPlanId = dayPlan.Id,
- DayOfWeek = dayPlan.DayOfWeek,
- Date = dayPlan.Date,
- SelectedMainIngredient = dayPlan.MainIngredient,
- BannedIngredients = dayPlan.BannedIngredients,
- RequiredIngredients = dayPlan.RequiredIngredients,
- AvailableMainIngredients = mainIngredients,
- SuggestedRecipes = suggestedRecipes,
- SelectedRecipeId = dayPlan.RecipeId
- };
- return View(viewModel);
- }
- [HttpPost]
- public async Task<IActionResult> UpdateDayPlan(DayPlanConfigViewModel model)
- {
- var dayPlan = await _context.DayPlans.FindAsync(model.DayPlanId);
- if (dayPlan == null)
- return NotFound();
- dayPlan.MainIngredient = model.SelectedMainIngredient;
- dayPlan.BannedIngredients = model.BannedIngredients;
- dayPlan.RequiredIngredients = model.RequiredIngredients;
- dayPlan.RecipeId = model.SelectedRecipeId;
- await _context.SaveChangesAsync();
- return RedirectToAction(nameof(Index));
- }
- [HttpPost]
- public async Task<IActionResult> RandomizeRecipe(int dayPlanId)
- {
- var dayPlan = await _context.DayPlans.FindAsync(dayPlanId);
- if (dayPlan == null)
- return NotFound();
- // Get all recipes for this week to avoid repeats
- var weekPlan = await _context.WeekPlans
- .Include(wp => wp.DayPlans)
- .FirstOrDefaultAsync(wp => wp.DayPlans.Any(dp => dp.Id == dayPlanId));
- var usedRecipeIds = weekPlan?.DayPlans
- .Where(dp => dp.RecipeId.HasValue && dp.Id != dayPlanId)
- .Select(dp => dp.RecipeId!.Value)
- .ToList() ?? new List<int>();
- var recipes = await GetSuggestedRecipesAsync(dayPlan.MainIngredient, dayPlan.BannedIngredients, dayPlan.RequiredIngredients);
- // Filter out already used recipes this week
- var availableRecipes = recipes.Where(r => !usedRecipeIds.Contains(r.Id)).ToList();
- // If no unused recipes, fall back to all suggested recipes
- if (!availableRecipes.Any())
- availableRecipes = recipes;
- if (availableRecipes.Any())
- {
- // Use cryptographically secure random for better distribution
- using var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
- var randomBytes = new byte[4];
- rng.GetBytes(randomBytes);
- var randomIndex = Math.Abs(BitConverter.ToInt32(randomBytes, 0)) % availableRecipes.Count;
- var randomRecipe = availableRecipes[randomIndex];
- dayPlan.RecipeId = randomRecipe.Id;
- await _context.SaveChangesAsync();
- }
- return RedirectToAction(nameof(Index));
- }
- private async Task<List<string>> GetMainIngredientsAsync()
- {
- // Define clean, consolidated main ingredient categories
- var mainIngredientCategories = new List<string>
- {
- "Pasta",
- "Potatis",
- "Ris",
- "Kött",
- "Kyckling",
- "Fisk",
- "Skaldjur",
- "Ägg",
- "Bönor & Linser",
- "Vegetariskt",
- "Soppa",
- "Sallad"
- };
- return mainIngredientCategories;
- }
- private async Task<List<Recipe>> GetSuggestedRecipesAsync(string? mainIngredient, string? bannedIngredients, string? requiredIngredients)
- {
- var query = _context.Recipes
- .Include(r => r.RecipeIngredients)
- .ThenInclude(ri => ri.Ingredient)
- .AsQueryable();
- // Filter by main ingredient with smart categorization
- if (!string.IsNullOrEmpty(mainIngredient))
- {
- var searchTerms = GetIngredientSearchTerms(mainIngredient);
- query = query.Where(r => r.RecipeIngredients
- .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null &&
- searchTerms.Any(term => ri.Ingredient.Name.ToLower().Contains(term))));
- }
- // Filter out banned ingredients
- if (!string.IsNullOrEmpty(bannedIngredients))
- {
- var banned = bannedIngredients.Split(',', StringSplitOptions.RemoveEmptyEntries)
- .Select(b => b.Trim().ToLower()).ToList();
- query = query.Where(r => !r.RecipeIngredients
- .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null && banned.Any(b => ri.Ingredient.Name!.ToLower().Contains(b))));
- }
- // Filter by required ingredients
- if (!string.IsNullOrEmpty(requiredIngredients))
- {
- var required = requiredIngredients.Split(',', StringSplitOptions.RemoveEmptyEntries)
- .Select(req => req.Trim().ToLower()).ToList();
- foreach (var req in required)
- {
- query = query.Where(r => r.RecipeIngredients
- .Any(ri => ri.Ingredient != null && ri.Ingredient.Name != null && ri.Ingredient.Name.ToLower().Contains(req)));
- }
- }
- // Increase the pool significantly and add randomization at DB level
- return await query.OrderBy(r => Guid.NewGuid()).Take(50).ToListAsync();
- }
- private List<string> GetIngredientSearchTerms(string mainIngredient)
- {
- return mainIngredient.ToLower() switch
- {
- "pasta" => new List<string> { "pasta", "spaghetti", "penne", "fusilli", "linguine", "tagliatelle", "fettuccine", "makaroner", "lasagne", "ravioli", "gnocchi" },
- "potatis" => new List<string> { "potatis", "potato", "potatismos", "klyftpotatis", "gratäng" },
- "ris" => new List<string> { "ris", "rice", "risotto", "basmati", "jasmin" },
- "kött" => new List<string> { "kött", "nötkött", "fläsk", "lamm", "köttfärs", "bacon", "skinka", "korv" },
- "kyckling" => new List<string> { "kyckling", "chicken", "kycklingfilé", "kycklingklubb", "kycklingbröst" },
- "fisk" => new List<string> { "fisk", "lax", "torsk", "abborre", "tonfisk", "salmon", "cod" },
- "skaldjur" => new List<string> { "räkor", "krabba", "hummer", "musslor", "shrimp", "crab", "lobster" },
- "ägg" => new List<string> { "ägg", "egg", "omelett", "scrambled" },
- "bönor & linser" => new List<string> { "bönor", "linser", "kikärtor", "beans", "lentils", "chickpeas" },
- "vegetariskt" => new List<string> { "tofu", "halloumi", "quinoa", "bulgur", "tempeh", "vegetarisk" },
- "soppa" => new List<string> { "soppa", "soup", "buljong", "bisque" },
- "sallad" => new List<string> { "sallad", "salad", "coleslaw", "caesar" },
- _ => new List<string> { mainIngredient.ToLower() }
- };
- }
- }
- }
|