This commit is contained in:
2025-09-11 00:02:38 +02:00
parent e78ea9da35
commit 902c8c52a9
6 changed files with 122 additions and 89 deletions

View File

@@ -84,6 +84,7 @@ public class HelloFreshController : AppController
}
}
// ❌ Supprime la limite de 3 portions
[HttpPost("SaveRecipe")]
public IActionResult SaveRecipe([FromBody] string idSavingRecette)
{
@@ -94,7 +95,6 @@ public class HelloFreshController : AppController
if (string.IsNullOrWhiteSpace(idSavingRecette)) return BadRequest("Id invalide.");
var current = _context.SavingRecettes.Count(s => s.UserId == userId && s.IdSavingRecette == idSavingRecette);
if (current >= 3) return Ok(new { message = "cap", qty = 3 });
_context.SavingRecettes.Add(new SavingRecette { IdSavingRecette = idSavingRecette, UserId = userId.Value });
_context.SaveChanges();
@@ -115,12 +115,10 @@ public class HelloFreshController : AppController
var sessionUserId = HttpContext.Session.GetInt32("UserId");
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
// petite fonction pour normaliser les clés de regroupement par nom
static string Key(string? s) => (s ?? "").Trim().ToLowerInvariant();
if (isUserImportant)
{
// Mes recettes : on inner-join sur Recettes, on matérialise puis on regroupe par NOM
var rows = (
from s in _context.SavingRecettes.AsNoTracking()
where s.UserId == sessionUserId
@@ -141,7 +139,6 @@ public class HelloFreshController : AppController
.GroupBy(x => Key(x.Name))
.Select(g =>
{
// on choisit un représentant qui a un PDF si possible
var withPdf = g.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Pdf));
var pick = withPdf ?? g.OrderByDescending(x => x.Id).First();
@@ -163,7 +160,6 @@ public class HelloFreshController : AppController
}
else
{
// Tous les utilisateurs : on joint aussi sur Users, puis on regroupe par NOM
var rows = (
from s in _context.SavingRecettes.AsNoTracking()
join r in _context.Recettes.AsNoTracking() on s.IdSavingRecette equals r.Id
@@ -212,7 +208,6 @@ public class HelloFreshController : AppController
}
}
[HttpPost("DeleteRecipesOwned")]
public IActionResult DeleteRecipesOwned([FromBody] string idSavingRecette)
{
@@ -287,7 +282,7 @@ public class HelloFreshController : AppController
client.DefaultRequestHeaders.Accept.ParseAdd("application/pdf");
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("fr-FR,fr;q=0.9,en;q=0.8");
using var resp = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
using var resp = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
if (!resp.IsSuccessStatusCode)
{
var preview = await resp.Content.ReadAsStringAsync();
@@ -301,17 +296,16 @@ public class HelloFreshController : AppController
|| uri.AbsolutePath.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase);
if (!isPdf) return BadRequest($"Le document récupéré n'est pas un PDF. Content-Type: {mediaType}");
await using var upstream = await resp.Content.ReadAsStreamAsync(HttpContext.RequestAborted);
var ms = new MemoryStream();
await resp.Content.CopyToAsync(ms);
await upstream.CopyToAsync(ms, HttpContext.RequestAborted);
ms.Position = 0;
if (resp.Content.Headers.ContentLength is long len)
Response.ContentLength = len;
Response.Headers["Cache-Control"] = "public, max-age=3600";
Response.Headers["Content-Disposition"] = "inline; filename=\"recette.pdf\"";
return File(ms, "application/pdf");
// ✅ Range activé → scroll multi-pages iOS/iframe/pdf.js OK
return new FileStreamResult(ms, "application/pdf") { EnableRangeProcessing = true };
}
catch (TaskCanceledException)
{
@@ -417,17 +411,21 @@ public class HelloFreshController : AppController
.Select(r => new { r.Id, r.Ingredients })
.ToList();
var portionById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Min(4, Math.Max(1, x.Portions)));
// ✅ on ne borne plus à 4 pour le calcul; si >4, on prend la table "4" et on scale
var portionsById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Max(1, x.Portions));
var rawItems = new List<IngredientItemDto>();
foreach (var r in recettes)
{
if (!portionById.TryGetValue(r.Id, out var portions)) portions = 1;
if (!portionsById.TryGetValue(r.Id, out var portions)) portions = 1;
int basePortions = Math.Min(4, Math.Max(1, portions));
double scale = portions <= 4 ? 1.0 : (double)portions / 4.0;
try
{
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
if (!doc.RootElement.TryGetProperty(portions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
if (!doc.RootElement.TryGetProperty(basePortions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
continue;
foreach (var el in arr.EnumerateArray())
@@ -435,9 +433,22 @@ public class HelloFreshController : AppController
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
if (string.IsNullOrWhiteSpace(name)) continue;
// ✅ scale si >4 et quantité numérique
if (scale != 1.0 && !string.IsNullOrWhiteSpace(qty))
{
var parsed = ParseQuantity(qty);
if (parsed.Value is double v)
{
var scaled = v * scale;
var qtyStr = ((scaled % 1.0) == 0.0)
? scaled.ToString("0", System.Globalization.CultureInfo.InvariantCulture)
: scaled.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
qty = string.IsNullOrWhiteSpace(parsed.Unit) ? qtyStr : $"{qtyStr} {parsed.Unit}";
}
}
rawItems.Add(new IngredientItemDto
{
Name = name.Trim(),
@@ -483,7 +494,6 @@ public class HelloFreshController : AppController
var userId = HttpContext.Session.GetInt32("UserId");
if (userId == null) return Unauthorized("Utilisateur non connecté.");
// 1) Reconstituer la liste agrégée comme dans AggregatedIngredients
var ownedNames = _context.Ingredients.Select(i => i.NameOwnedIngredients)
.ToList()
.Select(s => (s ?? "").Trim().ToLowerInvariant())
@@ -503,16 +513,20 @@ public class HelloFreshController : AppController
.Select(r => new { r.Id, r.Ingredients })
.ToList();
var portionById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Min(4, Math.Max(1, x.Portions)));
var portionsById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Max(1, x.Portions));
var rawItems = new List<IngredientItemDto>();
foreach (var r in recettes)
{
if (!portionById.TryGetValue(r.Id, out var portions)) portions = 1;
if (!portionsById.TryGetValue(r.Id, out var portions)) portions = 1;
int basePortions = Math.Min(4, Math.Max(1, portions));
double scale = portions <= 4 ? 1.0 : (double)portions / 4.0;
try
{
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
if (!doc.RootElement.TryGetProperty(portions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
if (!doc.RootElement.TryGetProperty(basePortions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
continue;
foreach (var el in arr.EnumerateArray())
@@ -520,8 +534,21 @@ public class HelloFreshController : AppController
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
if (string.IsNullOrWhiteSpace(name)) continue;
if (scale != 1.0 && !string.IsNullOrWhiteSpace(qty))
{
var parsed = ParseQuantity(qty);
if (parsed.Value is double v)
{
var scaled = v * scale;
var qtyStr = ((scaled % 1.0) == 0.0)
? scaled.ToString("0", System.Globalization.CultureInfo.InvariantCulture)
: scaled.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
qty = string.IsNullOrWhiteSpace(parsed.Unit) ? qtyStr : $"{qtyStr} {parsed.Unit}";
}
}
rawItems.Add(new IngredientItemDto
{
Name = name.Trim(),
@@ -537,9 +564,8 @@ public class HelloFreshController : AppController
var aggregated = AggregateIngredients(rawItems, ownedNames);
var allowedNames = aggregated.Select(a => a.Name).Distinct().ToList();
// 2) Appel interne à notre IA selector /api/ai/select (même host)
var client = httpClientFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(60); // on laisse confortable
client.Timeout = TimeSpan.FromSeconds(60);
var baseUrl = $"{Request.Scheme}://{Request.Host}";
var reqBody = new
@@ -559,7 +585,6 @@ public class HelloFreshController : AppController
var raw = await resp.Content.ReadAsStringAsync();
if (!resp.IsSuccessStatusCode)
{
// fallback live (contient)
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
var fallback = string.IsNullOrEmpty(nq)
? aggregated
@@ -577,7 +602,6 @@ public class HelloFreshController : AppController
? nEl.EnumerateArray().Select(x => x.GetString() ?? "").Where(s => !string.IsNullOrWhiteSpace(s)).ToList()
: new List<string>();
// 3) Filtrer/ordonner selon la réponse IA
var order = names.Select((n, i) => new { n = n.Trim().ToLowerInvariant(), i })
.ToDictionary(x => x.n, x => x.i);
var selected = aggregated
@@ -585,7 +609,6 @@ public class HelloFreshController : AppController
.OrderBy(a => order[(a.Name ?? "").Trim().ToLowerInvariant()])
.ToList();
// Fallback si lIA ne renvoie rien
if (selected.Count == 0)
{
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
@@ -625,7 +648,7 @@ public class HelloFreshController : AppController
image = r.Image,
difficulte = r.Difficulte,
ingredientsJson = r.Ingredients,
pdf = r.Pdf // 🟢 important pour le fallback
pdf = r.Pdf
})
.ToList();
@@ -676,10 +699,10 @@ public class HelloFreshController : AppController
return StatusCode(500, new { message = "Erreur serveur", error = ex.Message });
}
}
[HttpGet("GetAllRecipes")]
public IActionResult GetAllRecipes()
{
// 1) Comptages par recette et utilisateur
var counts = _context.SavingRecettes
.AsNoTracking()
.GroupBy(s => new { s.IdSavingRecette, s.UserId })
@@ -691,8 +714,6 @@ public class HelloFreshController : AppController
})
.ToList();
// 2) Recettes ✅ AJOUTE le champ JSON "IngredientsToHad"
// ⚠️ adapte "r.IngredientsToHad" si ton modèle a un autre nom (ex: IngredientsToHaveAtHome / Pantry / etc.)
var recettes = _context.Recettes
.AsNoTracking()
.Select(r => new
@@ -702,11 +723,10 @@ public class HelloFreshController : AppController
r.Image,
r.TempsDePreparation,
r.Tags,
IngredientsToHad = r.IngredientsToHad // <— ICI
IngredientsToHad = r.IngredientsToHad
})
.ToList();
// 3) Users
var neededUserIds = counts.Select(x => x.UserId).Distinct().ToList();
var users = _layout.Users
@@ -715,7 +735,6 @@ public class HelloFreshController : AppController
.Select(u => new { u.Id, u.Username })
.ToList();
// 4) Join en mémoire ✅ propage "IngredientsToHad"
var baseRows =
from c in counts
join r in recettes on c.RecetteId equals r.Id
@@ -729,11 +748,10 @@ public class HelloFreshController : AppController
r.TempsDePreparation,
c.Portions,
r.Tags,
r.IngredientsToHad, // <— ICI
r.IngredientsToHad,
User = u?.Username
};
// 5) Regroupement final par RecetteId
var result = baseRows
.GroupBy(x => x.RecetteId)
.Select(g => new
@@ -748,8 +766,6 @@ public class HelloFreshController : AppController
.Select(x => x.User)
.Distinct()
.ToList(),
// ✅ expose le JSON côté API en camelCase pour le front
ingredientsToHad = g.First().IngredientsToHad
})
.ToList();
@@ -757,9 +773,6 @@ public class HelloFreshController : AppController
return Ok(result);
}
[HttpGet("/HelloFresh/GetAllRecipesWithUsers")]
public IActionResult GetAllRecipesWithUsers()
{
@@ -787,8 +800,6 @@ public class HelloFreshController : AppController
return Ok(data);
}
[HttpGet("GetHistory")]
public IActionResult GetHistory([FromQuery] bool isUserImportant = true, [FromQuery] string? search = null)
{
@@ -798,7 +809,6 @@ public class HelloFreshController : AppController
if (isUserImportant && sessionUserId == null)
return Unauthorized("Utilisateur non connecté.");
// 1) Historique (matérialisé)
var histQ = _context.HistoriqueRecettes.AsNoTracking();
if (isUserImportant) histQ = histQ.Where(h => h.UserId == sessionUserId);
@@ -806,7 +816,6 @@ public class HelloFreshController : AppController
.Select(h => new { h.DateHistorique, h.RecetteId, h.UserId })
.ToList();
// 2) Dictionnaire Recettes avec clé normalisée (trim+lower) pour éviter tout souci de casse/espaces
string K(string? s) => (s ?? "").Trim().ToLowerInvariant();
var recDict = _context.Recettes.AsNoTracking()
.Select(r => new { r.Id, r.Name, r.Image, r.TempsDePreparation })
@@ -814,7 +823,6 @@ public class HelloFreshController : AppController
.GroupBy(r => K(r.Id))
.ToDictionary(g => g.Key, g => g.First());
// 3) “Join” en mémoire (pas de ?. dans lexpression EF)
var joined = hist.Select(h =>
{
recDict.TryGetValue(K(h.RecetteId), out var r);
@@ -829,14 +837,12 @@ public class HelloFreshController : AppController
};
}).ToList();
// 4) Filtre recherche serveur (optionnel)
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.Trim().ToLowerInvariant();
joined = joined.Where(x => (x.Name ?? "").ToLowerInvariant().Contains(s)).ToList();
}
// 5) Noms dutilisateurs si “tous”
var userNames = new Dictionary<int, string>();
if (!isUserImportant)
{
@@ -849,7 +855,6 @@ public class HelloFreshController : AppController
.ToDictionary(g => g.Key, g => g.First().Username ?? $"User#{g.Key}");
}
// 6) Regroupement par Date + Recette
var rows = joined
.GroupBy(x => new { x.Date, x.RecetteId })
.Select(g => new
@@ -868,7 +873,6 @@ public class HelloFreshController : AppController
})
.ToList();
// 7) Mise en forme par date
var culture = new System.Globalization.CultureInfo("fr-FR");
var result = rows
.GroupBy(x => x.Date)
@@ -889,8 +893,6 @@ public class HelloFreshController : AppController
}
}
[HttpPost("ArchiveAll")]
public IActionResult ArchiveAll()
{
@@ -899,7 +901,6 @@ public class HelloFreshController : AppController
try
{
// 1) Récup selections de l'utilisateur
var selections = _context.SavingRecettes
.Where(s => s.UserId == userId)
.AsNoTracking()
@@ -908,16 +909,14 @@ public class HelloFreshController : AppController
if (selections.Count == 0)
return Ok(new { message = "empty", archivedPortions = 0, archivedRecipes = 0, skippedIds = Array.Empty<string>() });
// 2) Groupes par recette (id string) = nb de portions
var groups = selections
.GroupBy(s => s.IdSavingRecette)
.Select(g => new { RecetteId = g.Key, Portions = g.Count() })
.ToList();
// 3) Filtrer sur ids valides (évite FK/erreurs)
var validIds = _context.Recettes
.AsNoTracking()
.Select(r => r.Id) // string
.Select(r => r.Id)
.ToHashSet(StringComparer.Ordinal);
var validGroups = groups.Where(g => validIds.Contains(g.RecetteId)).ToList();
@@ -926,7 +925,6 @@ public class HelloFreshController : AppController
if (validGroups.Count == 0)
return Ok(new { message = "nothing_valid", archivedPortions = 0, archivedRecipes = 0, skippedIds });
// 4) Date Paris (DateOnly)
DateOnly todayParis;
try
{
@@ -937,7 +935,6 @@ public class HelloFreshController : AppController
using var tx = _context.Database.BeginTransaction();
// 5) Préparer les insertions (1 ligne par portion)
var toInsert = new List<HistoriqueRecette>(capacity: validGroups.Sum(x => x.Portions));
foreach (var g in validGroups)
{
@@ -945,14 +942,13 @@ public class HelloFreshController : AppController
{
toInsert.Add(new HistoriqueRecette
{
RecetteId = g.RecetteId, // string
RecetteId = g.RecetteId,
UserId = userId.Value,
DateHistorique = todayParis
});
}
}
// 6) Insert + Delete (seulement les rows correspondants aux ids valides)
if (toInsert.Count > 0)
_context.HistoriqueRecettes.AddRange(toInsert);
@@ -962,11 +958,9 @@ public class HelloFreshController : AppController
_context.SavingRecettes.RemoveRange(rowsToDelete);
var archivedPortions = _context.SaveChanges(); // compte total opérations (insert + delete)
_context.SaveChanges();
tx.Commit();
// NB: archivedPortions = insertions + suppressions (EF renvoie le nb total de rows affectées)
// on renvoie plutôt nos compteurs fiables :
var archivedRecipes = validGroups.Count;
var archivedPieces = validGroups.Sum(g => g.Portions);
@@ -980,7 +974,6 @@ public class HelloFreshController : AppController
}
catch (DbUpdateException dbex)
{
// remonte lessentiel pour debug front/logs
return StatusCode(500, new
{
message = "archive_failed",
@@ -1001,17 +994,15 @@ public class HelloFreshController : AppController
// POST /HelloFresh/HasHistory
[HttpPost("HasHistory")]
[IgnoreAntiforgeryToken] // garde si tu postes depuis du JS sans token
[IgnoreAntiforgeryToken]
public async Task<IActionResult> HasHistory([FromBody] string recipeId)
{
// même logique que le reste de ton contrôleur : user via Session
var sessionUserId = HttpContext.Session.GetInt32("UserId");
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
if (string.IsNullOrWhiteSpace(recipeId))
return Ok(new { exists = false });
// ⚠️ compare sur la bonne colonne (RecetteId) et type string
bool exists = await _context.HistoriqueRecettes
.AsNoTracking()
.AnyAsync(h => h.RecetteId == recipeId && h.UserId == sessionUserId.Value);
@@ -1064,6 +1055,7 @@ public class HelloFreshController : AppController
var list = tags.OrderBy(s => s, StringComparer.Create(new System.Globalization.CultureInfo("fr-FR"), true)).ToList();
return Ok(list);
}
// API: recettes choisies distinctes par nom, avec PDF
[HttpGet("GetOwnedForReader")]
public IActionResult GetOwnedForReader()
@@ -1071,7 +1063,6 @@ public class HelloFreshController : AppController
var userId = HttpContext.Session.GetInt32("UserId");
if (userId == null) return Unauthorized("Utilisateur non connecté.");
// 1) portions par recette de l'utilisateur
var owned = _context.SavingRecettes
.Where(s => s.UserId == userId)
.GroupBy(s => s.IdSavingRecette)
@@ -1080,7 +1071,6 @@ public class HelloFreshController : AppController
if (owned.Count == 0) return Ok(Array.Empty<object>());
// 2) join recettes (on ramène le PDF)
var joined = owned
.Join(_context.Recettes,
g => g.RecetteId,
@@ -1096,7 +1086,6 @@ public class HelloFreshController : AppController
})
.ToList();
// 3) dédoublonnage par nom (insensible à la casse/espaces) — on garde celui avec + de portions puis nom ASC
string Key(string s) => (s ?? "").Trim().ToLowerInvariant();
var distinct = joined
.GroupBy(x => Key(x.name))
@@ -1106,4 +1095,4 @@ public class HelloFreshController : AppController
return Ok(distinct);
}
}
}