Meal lab v0.1
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-07-03 22:00:56 +02:00
parent 45994a9439
commit 072369fa17
12 changed files with 3671 additions and 1 deletions

View File

@@ -197,5 +197,72 @@ namespace Aberwyn.Controllers
return RedirectToAction("Categories");
}
[HttpGet("/meal/lab")]
public IActionResult Lab(int? id)
{
if (id.HasValue)
{
var entry = _menuService.GetRecipeLabEntryById(id.Value);
if (entry == null) return NotFound();
return View("Lab", entry);
}
// Skapa ett tomt labb-entry för formulär
var newEntry = new RecipeLabEntry
{
Title = "",
Inspiration = "",
Notes = "",
Tags = ""
};
return View("Lab", newEntry);
}
[HttpPost("/meal/lab/create")]
public IActionResult CreateLabEntry(RecipeLabEntry entry)
{
entry.CreatedAt = DateTime.Now;
_menuService.AddLabEntry(entry);
return RedirectToAction("Lab", new { id = entry.Id });
}
[HttpPost("/meal/lab/save-ingredients")]
public IActionResult SaveLabIngredients(int RecipeLabEntryId, List<LabIngredient> Ingredients)
{
_menuService.SaveIngredientsForLabEntry(RecipeLabEntryId, Ingredients);
return RedirectToAction("Lab", new { id = RecipeLabEntryId });
}
[HttpPost("/meal/lab/addversion")]
public IActionResult AddLabVersion(RecipeLabVersion version)
{
if (!ModelState.IsValid)
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
var entry = _menuService.GetRecipeLabEntryById(version.RecipeLabEntryId);
if (entry == null) return NotFound();
version.CreatedAt = DateTime.Now;
// Kopiera nuvarande ingredienser
var copiedIngredients = entry.Ingredients
.Select(i => new LabVersionIngredient
{
Quantity = i.Quantity,
Item = i.Item
})
.ToList();
_menuService.SaveLabVersionWithIngredients(version, copiedIngredients);
return RedirectToAction("Lab", new { id = version.RecipeLabEntryId });
}
}
}

View File

@@ -47,6 +47,11 @@ namespace Aberwyn.Data
public DbSet<UserPreferences> UserPreferences { get; set; }
public DbSet<StoredPushSubscription> PushSubscriptions { get; set; }
public DbSet<MealCategory> MealCategories { get; set; }
public DbSet<RecipeLabEntry> RecipeLabEntries { get; set; }
public DbSet<RecipeLabVersion> RecipeLabVersions { get; set; }
public DbSet<LabIngredient> LabIngredients { get; set; }
public DbSet<LabVersionIngredient> LabVersionIngredients { get; set; }
}
}

View File

@@ -466,7 +466,7 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
}
_context.SaveChanges();
}
#region Lab
public void DeleteCategory(int id)
{
var cat = _context.MealCategories.Find(id);
@@ -476,6 +476,53 @@ public List<WeeklyMenu> GetWeeklyMenu(int weekNumber, int year)
_context.SaveChanges();
}
}
public RecipeLabEntry? GetRecipeLabEntryById(int id)
{
return _context.RecipeLabEntries
.Include(e => e.Versions)
.FirstOrDefault(e => e.Id == id);
}
public void AddVersionToLabEntry(RecipeLabVersion version)
{
_context.RecipeLabVersions.Add(version);
_context.SaveChanges();
}
public void AddLabEntry(RecipeLabEntry entry)
{
_context.RecipeLabEntries.Add(entry);
_context.SaveChanges();
}
public void SaveLabVersionWithIngredients(RecipeLabVersion version, List<LabVersionIngredient> ingredients)
{
_context.RecipeLabVersions.Add(version);
_context.SaveChanges(); // så vi får ett ID
foreach (var ing in ingredients)
{
ing.RecipeLabVersionId = version.Id;
}
_context.LabVersionIngredients.AddRange(ingredients);
_context.SaveChanges();
}
public void SaveIngredientsForLabEntry(int labEntryId, List<LabIngredient> ingredients)
{
var existing = _context.LabIngredients
.Where(i => i.RecipeLabEntryId == labEntryId)
.ToList();
_context.LabIngredients.RemoveRange(existing);
foreach (var ing in ingredients)
ing.RecipeLabEntryId = labEntryId;
_context.LabIngredients.AddRange(ingredients);
_context.SaveChanges();
}
#endregion
}
}

View File

@@ -0,0 +1,949 @@
// <auto-generated />
using System;
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Aberwyn.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250630123620_AddRecipeLab")]
partial class AddRecipeLab
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.36")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.AppSetting", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("AppSettings");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BudgetCategoryDefinitionId")
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryDefinitionId");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetCategoryDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<int?>("BudgetItemDefinitionId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("PaymentStatus")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.HasIndex("BudgetItemDefinitionId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultCategory")
.HasColumnType("longtext");
b.Property<int?>("DefaultPaymentStatus")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("BudgetItemDefinitions");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("MealId")
.HasColumnType("int");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("MealId");
b.ToTable("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CarbType")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<byte[]>("ImageData")
.HasColumnType("longblob");
b.Property<string>("ImageMimeType")
.HasColumnType("longtext");
b.Property<string>("ImageUrl")
.HasColumnType("longtext");
b.Property<string>("Instructions")
.HasColumnType("longtext");
b.Property<bool>("IsAvailable")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsPublished")
.HasColumnType("tinyint(1)");
b.Property<int?>("MealCategoryId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProteinType")
.HasColumnType("longtext");
b.Property<string>("RecipeUrl")
.HasColumnType("longtext");
b.Property<byte[]>("ThumbnailData")
.HasColumnType("longblob");
b.HasKey("Id");
b.HasIndex("MealCategoryId");
b.ToTable("Meals");
});
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Color")
.HasColumnType("longtext");
b.Property<string>("Description")
.HasColumnType("longtext");
b.Property<int>("DisplayOrder")
.HasColumnType("int");
b.Property<string>("Icon")
.HasColumnType("longtext");
b.Property<bool>("IsActive")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Slug")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("MealCategories");
b.HasData(
new
{
Id = 1,
Color = "#f97316",
DisplayOrder = 1,
Icon = "🍕",
IsActive = true,
Name = "Pizza",
Slug = "pizza"
});
});
modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CustomerName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("IngredientsJson")
.HasColumnType("longtext");
b.Property<DateTime>("OrderedAt")
.HasColumnType("datetime(6)");
b.Property<string>("PizzaName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PizzaOrders");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("PizzaOrderId")
.HasColumnType("int");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("PizzaOrderId");
b.HasIndex("UserId");
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BaseMealId")
.HasColumnType("int");
b.Property<string>("Category")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Inspiration")
.HasColumnType("longtext");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<int?>("Rating")
.HasColumnType("int");
b.Property<string>("Tags")
.HasColumnType("longtext");
b.Property<string>("TestedBy")
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("BaseMealId");
b.ToTable("RecipeLabEntries");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ingredients")
.HasColumnType("longtext");
b.Property<string>("Instructions")
.HasColumnType("longtext");
b.Property<int>("RecipeLabEntryId")
.HasColumnType("int");
b.Property<string>("ResultNotes")
.HasColumnType("longtext");
b.Property<string>("VersionLabel")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("RecipeLabEntryId");
b.ToTable("RecipeLabVersions");
});
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("PushSubscriptions");
});
modelBuilder.Entity("Aberwyn.Models.TodoTask", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AssignedTo")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("IsArchived")
.HasColumnType("tinyint(1)");
b.Property<int>("Priority")
.HasColumnType("int");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Tags")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("TodoTasks");
});
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<bool>("NotifyBudget")
.HasColumnType("tinyint(1)");
b.Property<bool>("NotifyMenu")
.HasColumnType("tinyint(1)");
b.Property<bool>("NotifyPizza")
.HasColumnType("tinyint(1)");
b.HasKey("UserId");
b.ToTable("UserPreferences");
});
modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BreakfastMealId")
.HasColumnType("int");
b.Property<string>("Cook")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<int>("DayOfWeek")
.HasColumnType("int");
b.Property<int?>("DinnerMealId")
.HasColumnType("int");
b.Property<int?>("LunchMealId")
.HasColumnType("int");
b.Property<int>("WeekNumber")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("WeeklyMenu", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition")
.WithMany()
.HasForeignKey("BudgetCategoryDefinitionId");
b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
.WithMany("Categories")
.HasForeignKey("BudgetPeriodId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetPeriod");
b.Navigation("Definition");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategory", null)
.WithMany("Items")
.HasForeignKey("BudgetCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition")
.WithMany()
.HasForeignKey("BudgetItemDefinitionId");
b.Navigation("BudgetItemDefinition");
});
modelBuilder.Entity("Aberwyn.Models.Ingredient", b =>
{
b.HasOne("Aberwyn.Models.Meal", null)
.WithMany("Ingredients")
.HasForeignKey("MealId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.HasOne("Aberwyn.Models.MealCategory", "Category")
.WithMany("Meals")
.HasForeignKey("MealCategoryId");
b.Navigation("Category");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder")
.WithMany()
.HasForeignKey("PizzaOrderId");
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("PizzaOrder");
b.Navigation("User");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
.WithMany()
.HasForeignKey("BaseMealId");
b.Navigation("BaseMeal");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
{
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
.WithMany("Versions")
.HasForeignKey("RecipeLabEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Aberwyn.Models.UserPreferences", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
.WithOne("Preferences")
.HasForeignKey("Aberwyn.Models.UserPreferences", "UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Navigation("Preferences")
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Navigation("Categories");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Navigation("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.MealCategory", b =>
{
b.Navigation("Meals");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.Navigation("Versions");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Aberwyn.Migrations
{
public partial class AddRecipeLab : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "RecipeLabEntries",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Title = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Category = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Inspiration = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
BaseMealId = table.Column<int>(type: "int", nullable: true),
Notes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Rating = table.Column<int>(type: "int", nullable: true),
TestedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Tags = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeLabEntries", x => x.Id);
table.ForeignKey(
name: "FK_RecipeLabEntries_Meals_BaseMealId",
column: x => x.BaseMealId,
principalTable: "Meals",
principalColumn: "Id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "RecipeLabVersions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
VersionLabel = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Ingredients = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Instructions = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ResultNotes = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeLabVersions", x => x.Id);
table.ForeignKey(
name: "FK_RecipeLabVersions_RecipeLabEntries_RecipeLabEntryId",
column: x => x.RecipeLabEntryId,
principalTable: "RecipeLabEntries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_RecipeLabEntries_BaseMealId",
table: "RecipeLabEntries",
column: "BaseMealId");
migrationBuilder.CreateIndex(
name: "IX_RecipeLabVersions_RecipeLabEntryId",
table: "RecipeLabVersions",
column: "RecipeLabEntryId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "RecipeLabVersions");
migrationBuilder.DropTable(
name: "RecipeLabEntries");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Aberwyn.Migrations
{
public partial class AddLabIngredientModel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "LabIngredients",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RecipeLabEntryId = table.Column<int>(type: "int", nullable: false),
Quantity = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Item = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_LabIngredients", x => x.Id);
table.ForeignKey(
name: "FK_LabIngredients_RecipeLabEntries_RecipeLabEntryId",
column: x => x.RecipeLabEntryId,
principalTable: "RecipeLabEntries",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "LabVersionIngredients",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RecipeLabVersionId = table.Column<int>(type: "int", nullable: false),
Quantity = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Item = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_LabVersionIngredients", x => x.Id);
table.ForeignKey(
name: "FK_LabVersionIngredients_RecipeLabVersions_RecipeLabVersionId",
column: x => x.RecipeLabVersionId,
principalTable: "RecipeLabVersions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_LabIngredients_RecipeLabEntryId",
table: "LabIngredients",
column: "RecipeLabEntryId");
migrationBuilder.CreateIndex(
name: "IX_LabVersionIngredients_RecipeLabVersionId",
table: "LabVersionIngredients",
column: "RecipeLabVersionId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LabIngredients");
migrationBuilder.DropTable(
name: "LabVersionIngredients");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Aberwyn.Migrations
{
public partial class AddRecipeLabModels : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Ingredients",
table: "RecipeLabVersions");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Ingredients",
table: "RecipeLabVersions",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
}
}

View File

@@ -264,6 +264,54 @@ namespace Aberwyn.Migrations
b.ToTable("Ingredients");
});
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("RecipeLabEntryId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipeLabEntryId");
b.ToTable("LabIngredients");
});
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Item")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Quantity")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("RecipeLabVersionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipeLabVersionId");
b.ToTable("LabVersionIngredients");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.Property<int>("Id")
@@ -428,6 +476,76 @@ namespace Aberwyn.Migrations
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("BaseMealId")
.HasColumnType("int");
b.Property<string>("Category")
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Inspiration")
.HasColumnType("longtext");
b.Property<string>("Notes")
.HasColumnType("longtext");
b.Property<int?>("Rating")
.HasColumnType("int");
b.Property<string>("Tags")
.HasColumnType("longtext");
b.Property<string>("TestedBy")
.HasColumnType("longtext");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("BaseMealId");
b.ToTable("RecipeLabEntries");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Instructions")
.HasColumnType("longtext");
b.Property<int>("RecipeLabEntryId")
.HasColumnType("int");
b.Property<string>("ResultNotes")
.HasColumnType("longtext");
b.Property<string>("VersionLabel")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("RecipeLabEntryId");
b.ToTable("RecipeLabVersions");
});
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
{
b.Property<int>("Id")
@@ -719,6 +837,28 @@ namespace Aberwyn.Migrations
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.LabIngredient", b =>
{
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
.WithMany("Ingredients")
.HasForeignKey("RecipeLabEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b =>
{
b.HasOne("Aberwyn.Models.RecipeLabVersion", "Version")
.WithMany("Ingredients")
.HasForeignKey("RecipeLabVersionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Version");
});
modelBuilder.Entity("Aberwyn.Models.Meal", b =>
{
b.HasOne("Aberwyn.Models.MealCategory", "Category")
@@ -745,6 +885,26 @@ namespace Aberwyn.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.HasOne("Aberwyn.Models.Meal", "BaseMeal")
.WithMany()
.HasForeignKey("BaseMealId");
b.Navigation("BaseMeal");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
{
b.HasOne("Aberwyn.Models.RecipeLabEntry", "Entry")
.WithMany("Versions")
.HasForeignKey("RecipeLabEntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", "User")
@@ -843,6 +1003,18 @@ namespace Aberwyn.Migrations
{
b.Navigation("Meals");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabEntry", b =>
{
b.Navigation("Ingredients");
b.Navigation("Versions");
});
modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b =>
{
b.Navigation("Ingredients");
});
#pragma warning restore 612, 618
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Aberwyn.Models
{
public class RecipeLabEntry
{
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string? Category { get; set; }
public string? Inspiration { get; set; }
public int? BaseMealId { get; set; }
[ForeignKey("BaseMealId")]
public Meal? BaseMeal { get; set; }
public string? Notes { get; set; }
public int? Rating { get; set; }
public string? TestedBy { get; set; }
public string? Tags { get; set; } // "vego,snabbt"
public DateTime CreatedAt { get; set; } = DateTime.Now;
public List<LabIngredient> Ingredients { get; set; } = new();
public List<RecipeLabVersion> Versions { get; set; } = new();
}
public class RecipeLabVersion
{
[Key]
public int Id { get; set; }
public int RecipeLabEntryId { get; set; }
[ForeignKey("RecipeLabEntryId")]
public RecipeLabEntry Entry { get; set; }
public string VersionLabel { get; set; } = "v1";
public List<LabVersionIngredient> Ingredients { get; set; } = new();
public string? Instructions { get; set; }
public string? ResultNotes { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
public class LabIngredient
{
[Key]
public int Id { get; set; }
public int RecipeLabEntryId { get; set; }
[ForeignKey("RecipeLabEntryId")]
public RecipeLabEntry Entry { get; set; }
public string Quantity { get; set; } = "";
public string Item { get; set; } = "";
}
public class LabVersionIngredient
{
public int Id { get; set; }
public int RecipeLabVersionId { get; set; }
[ForeignKey("RecipeLabVersionId")]
public RecipeLabVersion Version { get; set; }
public string Quantity { get; set; } = "";
public string Item { get; set; } = "";
}
}

View File

@@ -0,0 +1,99 @@
@model Aberwyn.Models.RecipeLabEntry
@{
ViewData["Title"] = "Matlabb";
}
<h1>Matlabb: @Model.Title</h1>
<form asp-action="CreateLabEntry" method="post">
<input type="hidden" asp-for="Id" />
<div>
<label>Titel</label>
<input asp-for="Title" class="form-control" />
</div>
<div>
<label>Kategori</label>
<input asp-for="Category" class="form-control" />
</div>
<div>
<label>Inspiration</label>
<input asp-for="Inspiration" class="form-control" />
</div>
<div>
<label>Anteckningar</label>
<textarea asp-for="Notes" class="form-control"></textarea>
</div>
<div>
<label>Taggar</label>
<input asp-for="Tags" class="form-control" />
</div>
<button type="submit" class="btn btn-primary mt-2">Spara info</button>
</form>
<hr />
<h3>Ingredienser</h3>
<form asp-action="SaveLabIngredients" method="post">
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
<div id="ingredient-list">
@for (int i = 0; i < Model.Ingredients.Count; i++)
{
<div class="d-flex mb-2">
<input type="text" name="Ingredients[@i].Quantity" value="@Model.Ingredients[i].Quantity" class="form-control me-1" placeholder="Mängd" />
<input type="text" name="Ingredients[@i].Item" value="@Model.Ingredients[i].Item" class="form-control me-1" placeholder="Ingrediens" />
</div>
}
</div>
<button type="submit" class="btn btn-success">Spara ingredienser</button>
</form>
<hr />
<h3>Versioner</h3>
<form asp-action="AddLabVersion" method="post">
<input type="hidden" name="RecipeLabEntryId" value="@Model.Id" />
<div class="mb-2">
<label>Versionsnamn</label>
<input type="text" name="VersionLabel" value="v@(@Model.Versions.Count + 1)" class="form-control" />
</div>
<button type="submit" class="btn btn-secondary">Skapa version från nuvarande ingredienser</button>
</form>
@if (Model.Versions.Any())
{
<div class="mt-4">
@foreach (var version in Model.Versions)
{
<div class="card mb-3">
<div class="card-header">
<strong>@version.VersionLabel</strong> <span class="text-muted">(@version.CreatedAt.ToShortDateString())</span>
</div>
<div class="card-body">
<h5>Ingredienser</h5>
@if (version.Ingredients != null && version.Ingredients.Any())
{
<ul>
@foreach (var ing in version.Ingredients)
{
<li>@ing.Quantity @ing.Item</li>
}
</ul>
}
else
{
<em>Inga ingredienser kopplade.</em>
}
<h5 class="mt-3">Instruktioner</h5>
<p>@version.Instructions</p>
<h5 class="mt-3">Kommentar</h5>
<p>@version.ResultNotes</p>
</div>
</div>
}
</div>
}