using Microsoft.EntityFrameworkCore; using YesChef.Api.Auth; using YesChef.Api.Data; using YesChef.Api.Entities; using YesChef.Api.Features.ShoppingLists; namespace YesChef.Api.Features.Recipes; public static class RecipeEndpoints { public record IngredientRequest(string Name, string? Quantity, int SortOrder, int? ProductId = null, int? FamilyProductId = null); public record CreateRecipeRequest(string Title, string? Description, string? Instructions, int? Servings, string? SourceUrl, List Ingredients); public record UpdateRecipeRequest(string Title, string? Description, string? Instructions, int? Servings, string? SourceUrl, List Ingredients); public static RouteGroupBuilder MapRecipeEndpoints(this RouteGroupBuilder group) { group.MapGet("/", async (YesChefDb db, HttpContext http, string? q) => { var familyId = http.User.GetFamilyId(); var query = db.Recipes.Where(r => r.FamilyId == familyId); if (!string.IsNullOrWhiteSpace(q)) query = query.Where(r => r.Title.Contains(q)); return await query.OrderByDescending(r => r.UpdatedAt) .Select(r => new { r.Id, r.Title, r.Description, r.Servings, IngredientCount = r.Ingredients.Count, r.UpdatedAt }) .ToListAsync(); }); group.MapPost("/", async (CreateRecipeRequest request, YesChefDb db, HttpContext http) => { var familyId = http.User.GetFamilyId(); foreach (var ing in request.Ingredients) { if (await ShoppingListEndpoints.ValidateProductLink(db, familyId, ing.ProductId, ing.FamilyProductId) is { } error) return error; } var recipe = new Recipe { FamilyId = familyId, Title = request.Title, Description = request.Description, Instructions = request.Instructions, Servings = request.Servings, SourceUrl = request.SourceUrl, CreatedByUserId = http.User.GetUserId(), Ingredients = request.Ingredients.Select(i => new RecipeIngredient { FamilyId = familyId, Name = i.Name, Quantity = i.Quantity, SortOrder = i.SortOrder, ProductId = i.ProductId, FamilyProductId = i.FamilyProductId }).ToList() }; db.Recipes.Add(recipe); await db.SaveChangesAsync(); return Results.Created($"/api/recipes/{recipe.Id}", new { recipe.Id, recipe.Title }); }); group.MapGet("/{id:int}", async (int id, YesChefDb db, HttpContext http) => { var familyId = http.User.GetFamilyId(); var recipe = await db.Recipes .Where(r => r.Id == id && r.FamilyId == familyId) .Include(r => r.Ingredients.OrderBy(i => i.SortOrder)) .Include(r => r.CreatedByUser) .FirstOrDefaultAsync(); if (recipe is null) return Results.NotFound(); return Results.Ok(new { recipe.Id, recipe.Title, recipe.Description, recipe.Instructions, recipe.Servings, recipe.SourceUrl, CreatedBy = recipe.CreatedByUser.Name, recipe.UpdatedAt, Ingredients = recipe.Ingredients.Select(i => new { i.Id, i.Name, i.Quantity, i.SortOrder, i.ProductId, i.FamilyProductId }) }); }); group.MapPut("/{id:int}", async (int id, UpdateRecipeRequest request, YesChefDb db, HttpContext http) => { var familyId = http.User.GetFamilyId(); var recipe = await db.Recipes .Where(r => r.Id == id && r.FamilyId == familyId) .Include(r => r.Ingredients) .FirstOrDefaultAsync(); if (recipe is null) return Results.NotFound(); foreach (var ing in request.Ingredients) { if (await ShoppingListEndpoints.ValidateProductLink(db, familyId, ing.ProductId, ing.FamilyProductId) is { } error) return error; } recipe.Title = request.Title; recipe.Description = request.Description; recipe.Instructions = request.Instructions; recipe.Servings = request.Servings; recipe.SourceUrl = request.SourceUrl; recipe.UpdatedAt = DateTime.UtcNow; db.RecipeIngredients.RemoveRange(recipe.Ingredients); recipe.Ingredients = request.Ingredients.Select(i => new RecipeIngredient { FamilyId = familyId, Name = i.Name, Quantity = i.Quantity, SortOrder = i.SortOrder, ProductId = i.ProductId, FamilyProductId = i.FamilyProductId }).ToList(); await db.SaveChangesAsync(); return Results.Ok(new { recipe.Id, recipe.Title }); }); group.MapDelete("/{id:int}", async (int id, YesChefDb db, HttpContext http) => { var familyId = http.User.GetFamilyId(); var recipe = await db.Recipes.FirstOrDefaultAsync(r => r.Id == id && r.FamilyId == familyId); if (recipe is null) return Results.NotFound(); db.Recipes.Remove(recipe); await db.SaveChangesAsync(); return Results.NoContent(); }); return group; } }