From 256ce76af1eb0d11870f77e3f5d994063fe94539 Mon Sep 17 00:00:00 2001 From: elias Date: Fri, 6 Jun 2025 12:57:49 +0200 Subject: [PATCH] Pizza notice --- Aberwyn/Controllers/PizzaController.cs | 7 +- Aberwyn/Controllers/PushController.cs | 33 +- Aberwyn/Controllers/UserController.cs | 61 ++ Aberwyn/Data/ApplicationDbContext.cs | 2 + Aberwyn/Data/PizzaNotificationService.cs | 69 ++ ...referencesAndPushSubscriptions.Designer.cs | 755 +++++++++++++++++ ..._AddUserPreferencesAndPushSubscriptions.cs | 76 ++ ...0439_AddPushSubscriberUserLink.Designer.cs | 772 ++++++++++++++++++ ...0250606100439_AddPushSubscriberUserLink.cs | 48 ++ .../ApplicationDbContextModelSnapshot.cs | 93 +++ Aberwyn/Models/ApplicationUser.cs | 2 + Aberwyn/Models/PushSubscriber.cs | 2 + Aberwyn/Models/User.cs | 10 - Aberwyn/Models/UserModel.cs | 47 ++ Aberwyn/Models/WeeklyMenuViewModel.cs | 3 +- Aberwyn/Program.cs | 5 +- Aberwyn/Views/Admin/Index.cshtml | 32 +- Aberwyn/Views/Shared/_LoginPartial.cshtml | 5 +- Aberwyn/Views/User/Profile.cshtml | 32 + Aberwyn/wwwroot/css/site.css | 10 +- Aberwyn/wwwroot/js/site.js | 10 +- 21 files changed, 2047 insertions(+), 27 deletions(-) create mode 100644 Aberwyn/Controllers/UserController.cs create mode 100644 Aberwyn/Data/PizzaNotificationService.cs create mode 100644 Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.Designer.cs create mode 100644 Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.cs create mode 100644 Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.Designer.cs create mode 100644 Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.cs delete mode 100644 Aberwyn/Models/User.cs create mode 100644 Aberwyn/Models/UserModel.cs create mode 100644 Aberwyn/Views/User/Profile.cshtml diff --git a/Aberwyn/Controllers/PizzaController.cs b/Aberwyn/Controllers/PizzaController.cs index f9cd16c..b74f569 100644 --- a/Aberwyn/Controllers/PizzaController.cs +++ b/Aberwyn/Controllers/PizzaController.cs @@ -2,6 +2,7 @@ using Aberwyn.Data; using Aberwyn.Models; using System.Linq; +using Aberwyn.Services; namespace Aberwyn.Controllers { @@ -9,11 +10,14 @@ namespace Aberwyn.Controllers { private readonly MenuService _menuService; private readonly ApplicationDbContext _context; + private readonly PizzaNotificationService _pizzaNotifier; - public PizzaController(MenuService menuService, ApplicationDbContext context) + public PizzaController(PizzaNotificationService pizzaNotifier,MenuService menuService, ApplicationDbContext context) { _menuService = menuService; _context = context; + _pizzaNotifier = pizzaNotifier; + } [HttpGet] @@ -50,6 +54,7 @@ namespace Aberwyn.Controllers _context.PizzaOrders.Add(order); _context.SaveChanges(); + _pizzaNotifier.NotifyPizzaSubscribersAsync(pizzaName, customerName); TempData["Success"] = "Beställningen har lagts!"; return RedirectToAction("Order"); diff --git a/Aberwyn/Controllers/PushController.cs b/Aberwyn/Controllers/PushController.cs index 3c1ef73..4dd98dc 100644 --- a/Aberwyn/Controllers/PushController.cs +++ b/Aberwyn/Controllers/PushController.cs @@ -3,6 +3,9 @@ using Aberwyn.Models; using Lib.Net.Http.WebPush; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Identity; +using Aberwyn.Services; + namespace Aberwyn.Controllers { @@ -12,19 +15,37 @@ namespace Aberwyn.Controllers { private readonly ApplicationDbContext _context; private readonly PushNotificationService _notificationService; + private readonly UserManager _userManager; + private readonly PizzaNotificationService _pizzaNotifier; - public PushController(ApplicationDbContext context, PushNotificationService notificationService) + + public PushController(ApplicationDbContext context, + PushNotificationService notificationService, + UserManager userManager, + PizzaNotificationService pizzaNotifier) { _context = context; _notificationService = notificationService; + _userManager = userManager; + _pizzaNotifier = pizzaNotifier; } + [HttpPost("notify-pizza")] + public async Task NotifyPizza() + { + var count = await _pizzaNotifier.NotifyPizzaSubscribersAsync("Capricciosa", User.Identity.Name); + + return Ok(new { message = $"Skickade pizzanotiser till {count} användare." }); + } + [HttpPost("subscribe")] public async Task Subscribe([FromBody] PushSubscription subscription) { var existing = await _context.PushSubscribers .FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint); + var user = await _userManager.GetUserAsync(User); + if (user == null) return Unauthorized(); if (existing == null) { @@ -32,7 +53,8 @@ namespace Aberwyn.Controllers { Endpoint = subscription.Endpoint, P256DH = subscription.Keys["p256dh"], - Auth = subscription.Keys["auth"] + Auth = subscription.Keys["auth"], + UserId = user.Id }; _context.PushSubscribers.Add(newSubscriber); @@ -62,7 +84,12 @@ namespace Aberwyn.Controllers return BadRequest("Titel och meddelande krävs."); } - var subscribers = await _context.PushSubscribers.ToListAsync(); + var subscribers = await _context.PushSubscribers + .Include(s => s.User) + .ThenInclude(u => u.Preferences) + .Where(s => s.User != null && s.User.Preferences.NotifyPizza) + .ToListAsync(); + var payload = $"{{\"title\":\"{message.Title}\",\"body\":\"{message.Body}\"}}"; int successCount = 0; diff --git a/Aberwyn/Controllers/UserController.cs b/Aberwyn/Controllers/UserController.cs new file mode 100644 index 0000000..914987a --- /dev/null +++ b/Aberwyn/Controllers/UserController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Aberwyn.Models; +using System.Threading.Tasks; +using Aberwyn.Data; + +namespace Aberwyn.Controllers +{ + [Authorize] + public class UserController : Controller + { + private readonly UserManager _userManager; + private readonly ApplicationDbContext _context; + + public UserController(UserManager userManager, ApplicationDbContext context) + { + _userManager = userManager; + _context = context; + } + + [HttpGet] + public async Task Profile() + { + var user = await _userManager.GetUserAsync(User); + var prefs = await _context.UserPreferences.FindAsync(user.Id) ?? new UserPreferences(); + + var model = new UserProfileViewModel + { + Name = user.UserName, + Email = user.Email, + NotifyPizza = prefs.NotifyPizza, + NotifyMenu = prefs.NotifyMenu, + NotifyBudget = prefs.NotifyBudget + }; + + return View(model); + } + + [HttpPost] + public async Task SaveProfile(UserProfileViewModel model) + { + var user = await _userManager.GetUserAsync(User); + var prefs = await _context.UserPreferences.FindAsync(user.Id); + + if (prefs == null) + { + prefs = new UserPreferences { UserId = user.Id }; + _context.UserPreferences.Add(prefs); + } + + prefs.NotifyPizza = model.NotifyPizza; + prefs.NotifyMenu = model.NotifyMenu; + prefs.NotifyBudget = model.NotifyBudget; + + await _context.SaveChangesAsync(); + + return RedirectToAction("Profile"); + } + } +} diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs index e5ece4e..5599d1c 100644 --- a/Aberwyn/Data/ApplicationDbContext.cs +++ b/Aberwyn/Data/ApplicationDbContext.cs @@ -31,6 +31,8 @@ namespace Aberwyn.Data public DbSet Meals { get; set; } public DbSet WeeklyMenus { get; set; } public DbSet Ingredients { get; set; } + public DbSet UserPreferences { get; set; } + public DbSet PushSubscriptions { get; set; } } } diff --git a/Aberwyn/Data/PizzaNotificationService.cs b/Aberwyn/Data/PizzaNotificationService.cs new file mode 100644 index 0000000..81d42a6 --- /dev/null +++ b/Aberwyn/Data/PizzaNotificationService.cs @@ -0,0 +1,69 @@ +using Aberwyn.Data; +using Microsoft.EntityFrameworkCore; +using WebPush; + +namespace Aberwyn.Services +{ + public class PizzaNotificationService + { + private readonly ApplicationDbContext _context; + private readonly PushNotificationService _push; + + public PizzaNotificationService(ApplicationDbContext context, PushNotificationService push) + { + Console.WriteLine("🍕 PizzaNotificationService constructor körs!"); + + _context = context; + _push = push; + } + + public async Task NotifyPizzaSubscribersAsync(string pizzaName = null, string userName = null) +{ + var title = "Ny pizzabeställning 🍕"; + var body = "En pizza har precis beställts!"; + + if (!string.IsNullOrWhiteSpace(pizzaName)) + body = $"Pizzan '{pizzaName}' har precis beställts!"; + + if (!string.IsNullOrWhiteSpace(userName)) + body += $" (av {userName})"; + + var payload = $@"{{""title"":""{title}"",""body"":""{body}""}}"; + + var subscribers = await _context.PushSubscribers + .Include(s => s.User) + .ThenInclude(u => u.Preferences) + .Where(s => s.User != null && + s.User.Preferences != null && + s.User.Preferences.NotifyPizza && + !string.IsNullOrEmpty(s.Endpoint) && + s.Endpoint.StartsWith("https://")) + .ToListAsync(); + + + int successCount = 0; + foreach (var sub in subscribers) + { + try + { + _push.SendNotification(sub.Endpoint, sub.P256DH, sub.Auth, payload); + successCount++; + } + catch (WebPushException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Gone || ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + Console.WriteLine($"🗑️ Ogiltig prenumeration tas bort: {sub.Endpoint}"); + _context.PushSubscribers.Remove(sub); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"❌ Misslyckades att skicka till {sub.Endpoint}: {ex.Message}"); + } + } + + + return successCount; +} + + } +} diff --git a/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.Designer.cs b/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.Designer.cs new file mode 100644 index 0000000..40f1680 --- /dev/null +++ b/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.Designer.cs @@ -0,0 +1,755 @@ +// +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("20250606061016_AddUserPreferencesAndPushSubscriptions")] + partial class AddUserPreferencesAndPushSubscriptions + { + 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("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("AppSettings"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetCategoryDefinitionId") + .HasColumnType("int"); + + b.Property("BudgetPeriodId") + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryDefinitionId"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetCategoryDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BudgetCategoryId") + .HasColumnType("int"); + + b.Property("BudgetItemDefinitionId") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryId"); + + b.HasIndex("BudgetItemDefinitionId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultCategory") + .HasColumnType("longtext"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetItemDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BudgetPeriods"); + }); + + modelBuilder.Entity("Aberwyn.Models.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CarbType") + .HasColumnType("longtext"); + + b.Property("Category") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ImageData") + .HasColumnType("longblob"); + + b.Property("ImageMimeType") + .HasColumnType("longtext"); + + b.Property("ImageUrl") + .HasColumnType("longtext"); + + b.Property("Instructions") + .HasColumnType("longtext"); + + b.Property("IsAvailable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProteinType") + .HasColumnType("longtext"); + + b.Property("RecipeUrl") + .HasColumnType("longtext"); + + b.Property("ThumbnailData") + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("Meals"); + }); + + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CustomerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IngredientsJson") + .HasColumnType("longtext"); + + b.Property("OrderedAt") + .HasColumnType("datetime(6)"); + + b.Property("PizzaName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PizzaOrders"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("Aberwyn.Models.TodoTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedTo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TodoTasks"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("NotifyBudget") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyMenu") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyPizza") + .HasColumnType("tinyint(1)"); + + b.HasKey("UserId"); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BreakfastMealId") + .HasColumnType("int"); + + b.Property("Cook") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("DinnerMealId") + .HasColumnType("int"); + + b.Property("LunchMealId") + .HasColumnType("int"); + + b.Property("WeekNumber") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("WeeklyMenu", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("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", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("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", "BudgetCategory") + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition") + .WithMany() + .HasForeignKey("BudgetItemDefinitionId"); + + b.Navigation("BudgetCategory"); + + 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.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", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.cs b/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.cs new file mode 100644 index 0000000..722ec26 --- /dev/null +++ b/Aberwyn/Migrations/20250606061016_AddUserPreferencesAndPushSubscriptions.cs @@ -0,0 +1,76 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddUserPreferencesAndPushSubscriptions : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PushSubscriptions", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Endpoint = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + P256DH = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Auth = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_PushSubscriptions", x => x.Id); + table.ForeignKey( + name: "FK_PushSubscriptions_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "UserPreferences", + columns: table => new + { + UserId = table.Column(type: "varchar(255)", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + NotifyPizza = table.Column(type: "tinyint(1)", nullable: false), + NotifyMenu = table.Column(type: "tinyint(1)", nullable: false), + NotifyBudget = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserPreferences", x => x.UserId); + table.ForeignKey( + name: "FK_UserPreferences_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_PushSubscriptions_UserId", + table: "PushSubscriptions", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushSubscriptions"); + + migrationBuilder.DropTable( + name: "UserPreferences"); + } + } +} diff --git a/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.Designer.cs b/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.Designer.cs new file mode 100644 index 0000000..9906330 --- /dev/null +++ b/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.Designer.cs @@ -0,0 +1,772 @@ +// +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("20250606100439_AddPushSubscriberUserLink")] + partial class AddPushSubscriberUserLink + { + 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("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("AppSettings"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetCategoryDefinitionId") + .HasColumnType("int"); + + b.Property("BudgetPeriodId") + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryDefinitionId"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetCategoryDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BudgetCategoryId") + .HasColumnType("int"); + + b.Property("BudgetItemDefinitionId") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryId"); + + b.HasIndex("BudgetItemDefinitionId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultCategory") + .HasColumnType("longtext"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetItemDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BudgetPeriods"); + }); + + modelBuilder.Entity("Aberwyn.Models.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CarbType") + .HasColumnType("longtext"); + + b.Property("Category") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ImageData") + .HasColumnType("longblob"); + + b.Property("ImageMimeType") + .HasColumnType("longtext"); + + b.Property("ImageUrl") + .HasColumnType("longtext"); + + b.Property("Instructions") + .HasColumnType("longtext"); + + b.Property("IsAvailable") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProteinType") + .HasColumnType("longtext"); + + b.Property("RecipeUrl") + .HasColumnType("longtext"); + + b.Property("ThumbnailData") + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("Meals"); + }); + + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CustomerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IngredientsJson") + .HasColumnType("longtext"); + + b.Property("OrderedAt") + .HasColumnType("datetime(6)"); + + b.Property("PizzaName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PizzaOrders"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("Aberwyn.Models.TodoTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedTo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TodoTasks"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("NotifyBudget") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyMenu") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyPizza") + .HasColumnType("tinyint(1)"); + + b.HasKey("UserId"); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BreakfastMealId") + .HasColumnType("int"); + + b.Property("Cook") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("DinnerMealId") + .HasColumnType("int"); + + b.Property("LunchMealId") + .HasColumnType("int"); + + b.Property("WeekNumber") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("WeeklyMenu", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("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", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("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", "BudgetCategory") + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition") + .WithMany() + .HasForeignKey("BudgetItemDefinitionId"); + + b.Navigation("BudgetCategory"); + + 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.PushSubscriber", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + 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", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.cs b/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.cs new file mode 100644 index 0000000..cd81a8d --- /dev/null +++ b/Aberwyn/Migrations/20250606100439_AddPushSubscriberUserLink.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddPushSubscriberUserLink : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserId", + table: "PushSubscribers", + type: "varchar(255)", + nullable: false, + defaultValue: "") + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_PushSubscribers_UserId", + table: "PushSubscribers", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_PushSubscribers_AspNetUsers_UserId", + table: "PushSubscribers", + column: "UserId", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_PushSubscribers_AspNetUsers_UserId", + table: "PushSubscribers"); + + migrationBuilder.DropIndex( + name: "IX_PushSubscribers_UserId", + table: "PushSubscribers"); + + migrationBuilder.DropColumn( + name: "UserId", + table: "PushSubscribers"); + } + } +} diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs index 35138ed..d71eed8 100644 --- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs @@ -356,11 +356,46 @@ namespace Aberwyn.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + b.HasKey("Id"); + b.HasIndex("UserId"); + b.ToTable("PushSubscribers"); }); + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscriptions"); + }); + modelBuilder.Entity("Aberwyn.Models.TodoTask", b => { b.Property("Id") @@ -400,6 +435,25 @@ namespace Aberwyn.Migrations b.ToTable("TodoTasks"); }); + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("NotifyBudget") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyMenu") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyPizza") + .HasColumnType("tinyint(1)"); + + b.HasKey("UserId"); + + b.ToTable("UserPreferences"); + }); + modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b => { b.Property("Id") @@ -606,6 +660,39 @@ namespace Aberwyn.Migrations .IsRequired(); }); + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + 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", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -657,6 +744,12 @@ namespace Aberwyn.Migrations .IsRequired(); }); + modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b => + { + b.Navigation("Preferences") + .IsRequired(); + }); + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => { b.Navigation("Items"); diff --git a/Aberwyn/Models/ApplicationUser.cs b/Aberwyn/Models/ApplicationUser.cs index 8ff1ac2..db78ace 100644 --- a/Aberwyn/Models/ApplicationUser.cs +++ b/Aberwyn/Models/ApplicationUser.cs @@ -4,6 +4,8 @@ public class ApplicationUser : IdentityUser { + public virtual UserPreferences Preferences { get; set; } + // Lägg till egna fält om du vill, t.ex. public string DisplayName { get; set; } } } diff --git a/Aberwyn/Models/PushSubscriber.cs b/Aberwyn/Models/PushSubscriber.cs index f8b2a46..31e86a9 100644 --- a/Aberwyn/Models/PushSubscriber.cs +++ b/Aberwyn/Models/PushSubscriber.cs @@ -6,6 +6,8 @@ public string Endpoint { get; set; } public string P256DH { get; set; } public string Auth { get; set; } + public string UserId { get; set; } + public virtual ApplicationUser User { get; set; } } public class PushMessageDto { diff --git a/Aberwyn/Models/User.cs b/Aberwyn/Models/User.cs deleted file mode 100644 index c00ed42..0000000 --- a/Aberwyn/Models/User.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Aberwyn.Models -{ - public class User - { - public int UserID { get; set; } - public string Username { get; set; } - public string Name { get; set; } - } - -} diff --git a/Aberwyn/Models/UserModel.cs b/Aberwyn/Models/UserModel.cs new file mode 100644 index 0000000..2aeb54d --- /dev/null +++ b/Aberwyn/Models/UserModel.cs @@ -0,0 +1,47 @@ +using System.ComponentModel.DataAnnotations; + +namespace Aberwyn.Models +{ + public class UserModel + { + public int UserID { get; set; } + public string Username { get; set; } + public string Name { get; set; } + } + public class UserPreferences + { + [Key] + public string UserId { get; set; } + + public bool NotifyPizza { get; set; } + public bool NotifyMenu { get; set; } + public bool NotifyBudget { get; set; } + + public virtual ApplicationUser User { get; set; } + } + + public class StoredPushSubscription + { + [Key] + public int Id { get; set; } + + public string UserId { get; set; } + + public string Endpoint { get; set; } + public string P256DH { get; set; } + public string Auth { get; set; } + + public virtual ApplicationUser User { get; set; } + } + + public class UserProfileViewModel + { + public string Name { get; set; } + public string Email { get; set; } + + public bool NotifyPizza { get; set; } + public bool NotifyMenu { get; set; } + public bool NotifyBudget { get; set; } + } + +} diff --git a/Aberwyn/Models/WeeklyMenuViewModel.cs b/Aberwyn/Models/WeeklyMenuViewModel.cs index fdb7bb8..30430c6 100644 --- a/Aberwyn/Models/WeeklyMenuViewModel.cs +++ b/Aberwyn/Models/WeeklyMenuViewModel.cs @@ -13,9 +13,8 @@ namespace Aberwyn.Models public List RecentEntries { get; set; } = new(); public List WeeklyMenus { get; set; } = new(); - public List AvailableCooks { get; set; } = new(); + public List AvailableCooks { get; set; } = new(); - // Ny lista fr versikt public List PreviousWeeks { get; set; } = new(); public class RecentMenuEntry { diff --git a/Aberwyn/Program.cs b/Aberwyn/Program.cs index 8a8757b..58dcb22 100644 --- a/Aberwyn/Program.cs +++ b/Aberwyn/Program.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Localization; using Aberwyn.Models; using Microsoft.AspNetCore.Identity; using System.Text.Json; +using Aberwyn.Services; var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) @@ -131,7 +132,7 @@ builder.Services.Configure(options => // Appens övriga tjänster builder.Services.AddScoped(); -builder.Services.AddSingleton(sp => +builder.Services.AddScoped(sp => { var config = sp.GetRequiredService(); return new PushNotificationService( @@ -141,6 +142,8 @@ builder.Services.AddSingleton(sp => ); }); +builder.Services.AddScoped(); + builder.Services.Configure(builder.Configuration.GetSection("Vapid")); builder.Services.ConfigureApplicationCookie(options => diff --git a/Aberwyn/Views/Admin/Index.cshtml b/Aberwyn/Views/Admin/Index.cshtml index 23cedc1..ba5ecb1 100644 --- a/Aberwyn/Views/Admin/Index.cshtml +++ b/Aberwyn/Views/Admin/Index.cshtml @@ -106,8 +106,12 @@ - - +
+

Skicka Pizza-notis

+ +
+ +
Importera från annan databas
@@ -146,6 +150,30 @@