diff --git a/Aberwyn/Controllers/MealMenuApiController.cs b/Aberwyn/Controllers/MealMenuApiController.cs index b62d317..32eb005 100644 --- a/Aberwyn/Controllers/MealMenuApiController.cs +++ b/Aberwyn/Controllers/MealMenuApiController.cs @@ -28,10 +28,10 @@ namespace Aberwyn.Controllers } [HttpGet("getPublishedMeals")] - public IActionResult GetPublishedMeals() + public IActionResult GetPublishedMeals([FromQuery] bool includeUnpublished = false) { var meals = _menuService.GetMeals() - .Where(m => m.IsPublished) // 🟱 filtrera hĂ€r! + .Where(m => includeUnpublished || m.IsPublished) .Select(m => new { m.Id, m.Name, @@ -44,6 +44,7 @@ namespace Aberwyn.Controllers } + [HttpGet("getMeals")] public IActionResult GetMeals() { diff --git a/Aberwyn/Controllers/MealRatingApiController.cs b/Aberwyn/Controllers/MealRatingApiController.cs new file mode 100644 index 0000000..0b8b63b --- /dev/null +++ b/Aberwyn/Controllers/MealRatingApiController.cs @@ -0,0 +1,79 @@ +ï»żusing Aberwyn.Data; +using Aberwyn.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Aberwyn.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class MealRatingApiController : ControllerBase + { + private readonly ApplicationDbContext _context; + private readonly UserManager _userManager; + + public MealRatingApiController(ApplicationDbContext context, UserManager userManager) + { + _context = context; + _userManager = userManager; + } + + [HttpGet("{mealId}")] + public async Task GetRating(int mealId) + { + var user = await _userManager.GetUserAsync(User); + if (user == null) return Unauthorized(); + + var rating = await _context.MealRatings + .FirstOrDefaultAsync(r => r.MealId == mealId && r.UserId == user.Id); + + return Ok(rating?.Rating ?? 0); + } + [HttpGet("average/{mealId}")] + public async Task GetAverageRating(int mealId) + { + var ratings = await _context.MealRatings + .Where(r => r.MealId == mealId) + .ToListAsync(); + + if (ratings.Count == 0) + return Ok(0); + + var avg = ratings.Average(r => r.Rating); + return Ok(avg); + } + + + [HttpPost] + public async Task SetRating([FromBody] MealRatingDto model) + { + var user = await _userManager.GetUserAsync(User); + if (user == null) return Unauthorized(); + + var existing = await _context.MealRatings + .FirstOrDefaultAsync(r => r.MealId == model.MealId && r.UserId == user.Id); + + if (existing != null) + { + existing.Rating = model.Rating; + existing.CreatedAt = DateTime.UtcNow; + } + else + { + _context.MealRatings.Add(new MealRating + { + MealId = model.MealId, + UserId = user.Id, + Rating = model.Rating, + CreatedAt = DateTime.UtcNow + }); + } + + await _context.SaveChangesAsync(); + return Ok(); + } + + } + +} diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs index 9d09f9e..869840d 100644 --- a/Aberwyn/Data/ApplicationDbContext.cs +++ b/Aberwyn/Data/ApplicationDbContext.cs @@ -51,6 +51,7 @@ namespace Aberwyn.Data public DbSet RecipeLabVersions { get; set; } public DbSet LabIngredients { get; set; } public DbSet LabVersionIngredients { get; set; } + public DbSet MealRatings { get; set; } } diff --git a/Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs b/Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs new file mode 100644 index 0000000..9e0f186 --- /dev/null +++ b/Aberwyn/Migrations/20250707125951_AddMealRating.Designer.cs @@ -0,0 +1,1060 @@ +ï»ż// +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("20250707125951_AddMealRating")] + partial class AddMealRating + { + 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.Property("PaymentStatus") + .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("DefaultPaymentStatus") + .HasColumnType("int"); + + 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.LabIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RecipeLabEntryId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RecipeLabEntryId"); + + b.ToTable("LabIngredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.LabVersionIngredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RecipeLabVersionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RecipeLabVersionId"); + + b.ToTable("LabVersionIngredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CarbType") + .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("IsPublished") + .HasColumnType("tinyint(1)"); + + b.Property("MealCategoryId") + .HasColumnType("int"); + + 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.HasIndex("MealCategoryId"); + + b.ToTable("Meals"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("Icon") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("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.MealRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("MealRatings"); + }); + + 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("PizzaOrderId") + .HasColumnType("int"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BaseMealId") + .HasColumnType("int"); + + b.Property("Category") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Inspiration") + .HasColumnType("longtext"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("Tags") + .HasColumnType("longtext"); + + b.Property("TestedBy") + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("BaseMealId"); + + b.ToTable("RecipeLabEntries"); + }); + + modelBuilder.Entity("Aberwyn.Models.RecipeLabVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Instructions") + .HasColumnType("longtext"); + + b.Property("RecipeLabEntryId") + .HasColumnType("int"); + + b.Property("ResultNotes") + .HasColumnType("longtext"); + + b.Property("VersionLabel") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RecipeLabEntryId"); + + b.ToTable("RecipeLabVersions"); + }); + + 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", 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.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") + .WithMany("Meals") + .HasForeignKey("MealCategoryId"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealRating", b => + { + b.HasOne("Aberwyn.Models.Meal", "Meal") + .WithMany() + .HasForeignKey("MealId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Meal"); + }); + + 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", 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"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + 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 + } + } +} diff --git a/Aberwyn/Migrations/20250707125951_AddMealRating.cs b/Aberwyn/Migrations/20250707125951_AddMealRating.cs new file mode 100644 index 0000000..0d8a04e --- /dev/null +++ b/Aberwyn/Migrations/20250707125951_AddMealRating.cs @@ -0,0 +1,49 @@ +ï»żusing System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddMealRating : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MealRatings", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + MealId = table.Column(type: "int", nullable: false), + UserId = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Rating = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MealRatings", x => x.Id); + table.ForeignKey( + name: "FK_MealRatings_Meals_MealId", + column: x => x.MealId, + principalTable: "Meals", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_MealRatings_MealId", + table: "MealRatings", + column: "MealId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MealRatings"); + } + } +} diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs index b4a443b..f21702a 100644 --- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs @@ -413,6 +413,32 @@ namespace Aberwyn.Migrations }); }); + modelBuilder.Entity("Aberwyn.Models.MealRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("MealRatings"); + }); + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => { b.Property("Id") @@ -868,6 +894,17 @@ namespace Aberwyn.Migrations b.Navigation("Category"); }); + modelBuilder.Entity("Aberwyn.Models.MealRating", b => + { + b.HasOne("Aberwyn.Models.Meal", "Meal") + .WithMany() + .HasForeignKey("MealId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Meal"); + }); + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => { b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder") diff --git a/Aberwyn/Models/MenuViewModel.cs b/Aberwyn/Models/MenuViewModel.cs index 0aea362..22d8586 100644 --- a/Aberwyn/Models/MenuViewModel.cs +++ b/Aberwyn/Models/MenuViewModel.cs @@ -177,5 +177,22 @@ public class WeeklyMenu public List Meals { get; set; } = new(); } + public class MealRating + { + public int Id { get; set; } + public int MealId { get; set; } + public string UserId { get; set; } + public int Rating { get; set; } + public DateTime CreatedAt { get; set; } + + public Meal Meal { get; set; } + } + public class MealRatingDto + { + public int MealId { get; set; } + public int Rating { get; set; } + } + + } diff --git a/Aberwyn/Views/Home/Index.cshtml b/Aberwyn/Views/Home/Index.cshtml index f7e1dd9..58d8754 100644 --- a/Aberwyn/Views/Home/Index.cshtml +++ b/Aberwyn/Views/Home/Index.cshtml @@ -19,44 +19,51 @@ {
- @if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) { -
- @if (Model.BreakfastThumbnail != null) - { - var b64 = Convert.ToBase64String(Model.BreakfastThumbnail); - - } -

Frukost: @Model.BreakfastMealName

-
- } +@if (!string.IsNullOrWhiteSpace(Model.BreakfastMealName)) { +
+

Frukost: + @Model.BreakfastMealName +

+ @if (Model.BreakfastThumbnail != null) + { + var b64 = Convert.ToBase64String(Model.BreakfastThumbnail); +
+ @Model.BreakfastMealName +
+ } +
+} - @if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) { -
- @if (Model.LunchThumbnail != null) - { - var b64 = Convert.ToBase64String(Model.LunchThumbnail); - - } -

Lunch: @Model.LunchMealName

-
- } +@if (!string.IsNullOrWhiteSpace(Model.LunchMealName)) { +
+

Lunch: + @Model.LunchMealName +

+ @if (Model.LunchThumbnail != null) + { + var b64 = Convert.ToBase64String(Model.LunchThumbnail); +
+ @Model.LunchMealName +
+ } +
+} + +@if (!string.IsNullOrWhiteSpace(Model.DinnerMealName)) { +
+

Middag: + @Model.DinnerMealName +

+ @if (Model.DinnerThumbnail != null) + { + var b64 = Convert.ToBase64String(Model.DinnerThumbnail); +
+ @Model.DinnerMealName +
+ } +
+} - @if (!string.IsNullOrWhiteSpace(Model.DinnerMealName)) { -
- @if (Model.DinnerThumbnail != null) - { - var b64 = Convert.ToBase64String(Model.DinnerThumbnail); - - } -

Middag: @Model.DinnerMealName

-
- } @if (ViewBag.RestaurantIsOpen as bool? == true) { diff --git a/Aberwyn/Views/Meal/Index.cshtml b/Aberwyn/Views/Meal/Index.cshtml index 8304d75..de02dd1 100644 --- a/Aberwyn/Views/Meal/Index.cshtml +++ b/Aberwyn/Views/Meal/Index.cshtml @@ -1,4 +1,11 @@ +@using Microsoft.AspNetCore.Http +@inject IHttpContextAccessor HttpContextAccessor + +@{ + bool isChef = HttpContextAccessor.HttpContext.User.IsInRole("Chef"); +} + @@ -6,7 +13,7 @@ - + diff --git a/Aberwyn/wwwroot/css/Welcome.css b/Aberwyn/wwwroot/css/Welcome.css index 1f8881d..c0ec9e4 100644 --- a/Aberwyn/wwwroot/css/Welcome.css +++ b/Aberwyn/wwwroot/css/Welcome.css @@ -46,16 +46,51 @@ margin-bottom: 28px; } -.meal-lines p { - font-size: 1.2rem; - margin: 8px 0; - color: #0f172a; +.meal-line { + margin-bottom: 0rem; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; } -.meal-lines strong { - color: #475569; + .meal-line p { + font-size: 1rem; + margin-bottom: 0.5rem; + } + + .meal-line a { + color: #007d36; + text-decoration: none; + font-weight: 500; + } + + .meal-line a:hover { + text-decoration: underline; + } + +.meal-large-thumb-container { + margin-top: 0rem; + width: 100%; + max-width: 300px; + height: 100px; + overflow: hidden; + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); } +.meal-large-thumb { + width: 100%; + max-width: 300px; + height: 160px; + object-fit: cover; + object-position: center%; + display: block; + border-radius: 8px; + margin-top: 0rem; +} + + .no-menu { font-size: 1.1rem; color: #64748b; diff --git a/Aberwyn/wwwroot/css/meal-gallery.css b/Aberwyn/wwwroot/css/meal-gallery.css index f5672a2..f78ddbc 100644 --- a/Aberwyn/wwwroot/css/meal-gallery.css +++ b/Aberwyn/wwwroot/css/meal-gallery.css @@ -24,30 +24,51 @@ body { margin: 0; } +/* Sökfält + checkbox */ .search-container { margin-top: 1rem; position: relative; width: 100%; - max-width: 500px; + max-width: 600px; margin-inline: auto; + display: flex; + align-items: center; + gap: 1rem; + background: #e9eef3; + padding: 0.5rem 1rem; + border-radius: 12px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } - .search-container input { - width: 100%; - padding: 8px 34px 8px 12px; - border-radius: 20px; + .search-container input[type="text"] { + flex-grow: 1; + padding: 8px 12px; + border-radius: 8px; border: 1px solid #ccc; font-size: 1rem; box-sizing: border-box; } .search-container i { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); color: #888; - font-size: 0.9rem; + font-size: 1rem; + } + +/* Checkboxdel */ +.toggle-published { + display: flex; + align-items: center; + gap: 0.4rem; + font-size: 0.9rem; + color: #222; + user-select: none; + white-space: nowrap; +} + + .toggle-published input[type="checkbox"] { + accent-color: #3399ff; + transform: scale(1.1); + cursor: pointer; } /* === Grid layout === */ @@ -120,22 +141,29 @@ body { /* === Mobilanpassning === */ @media (max-width: 600px) { .meal-gallery-grid { - grid-template-columns: repeat(2, 1fr); /* exakt 2 per rad */ + grid-template-columns: repeat(2, 1fr); } + .meal-gallery-header h1 { font-size: 1.6rem; } .search-container { + flex-direction: column; + align-items: stretch; + padding: 1rem; + gap: 0.75rem; max-width: 90%; } - .search-container input { - padding: 6px 30px 6px 10px; - font-size: 0.9rem; - border-radius: 18px; + .search-container input[type="text"] { + font-size: 0.95rem; } + .toggle-published { + justify-content: center; + } + .meal-card img { height: 130px; } @@ -157,3 +185,29 @@ body { font-size: 0.85rem; } } + + +.btn-create-meal { + background-color: #3399ff; + color: white; + padding: 8px 14px; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + text-decoration: none; + border: none; + cursor: pointer; + transition: background-color 0.2s ease; + white-space: nowrap; +} + + .btn-create-meal:hover { + background-color: #2389e0; + } + +@media (max-width: 600px) { + .btn-create-meal { + width: 100%; + text-align: center; + } +} diff --git a/Aberwyn/wwwroot/css/meal.css b/Aberwyn/wwwroot/css/meal.css new file mode 100644 index 0000000..68a0460 --- /dev/null +++ b/Aberwyn/wwwroot/css/meal.css @@ -0,0 +1,199 @@ +ï»ż.admin-actions a.btn, .admin-actions a.btn-outline { + font-size: 1rem; + padding: 0.5rem 1.2rem; +} + +.dragging { + outline: 2px dashed #6a0dad; +} + +.meal-image-wrapper { + position: relative; +} + +.meal-container { + max-width: 900px; + margin: 2rem auto; + background: #fff; + padding: 2rem; + border-radius: 16px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); + font-family: 'Segoe UI', sans-serif; +} + +.meal-header { + display: flex; + flex-direction: row; + gap: 1.5rem; + align-items: center; +} + +.meal-meta { + flex: 1; +} + +.meal-title { + font-size: 2.5rem; + font-weight: bold; + margin-bottom: 1rem; + color: #333; +} + +.meal-image { + width: 250px; + height: 250px; + object-fit: cover; + border-radius: 12px; + border: 1px solid #ccc; +} + +.description { + font-size: 1.1rem; + color: #555; + margin-top: 0.5rem; + margin-bottom: 1.5rem; +} + +.meal-details p { + font-size: 1rem; + color: #333; + margin: 0.3rem 0; +} + +.label { + font-weight: bold; + color: #6a0dad; +} + +.form-group { + margin-bottom: 1rem; +} + + .form-group label { + font-weight: 600; + margin-bottom: 0.3rem; + display: block; + } + +.form-control { + width: 100%; + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 6px; + font-size: 1rem; +} + +.buttons { + margin-top: 1.5rem; +} + +.btn, +.btn-outline { + background-color: #6a0dad; + color: white; + border: none; + padding: 0.6rem 1.4rem; + border-radius: 6px; + margin: 0.4rem; + cursor: pointer; + font-size: 1rem; + text-decoration: none; +} + +.btn-outline { + background-color: transparent; + border: 2px solid #6a0dad; + color: #6a0dad; +} + +.recipe-link { + color: #6a0dad; + text-decoration: underline; + font-weight: 500; +} + +.placeholder { + color: #999; + font-style: italic; +} + +.ingredient-row { + display: flex; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.ingredient-qty { + flex: 1 0 30%; +} + +.ingredient-item { + flex: 2 0 60%; +} + +@@media (max-width: 768px) { + .meal-container { + padding: 1rem; + } + + .meal-header { + flex-direction: column; + align-items: center; + text-align: center; + } + + .meal-meta { + width: 100%; + } + + .meal-title { + font-size: 2rem; + } + + .meal-image { + width: 100%; + max-width: 300px; + height: auto; + } + + .form-control { + font-size: 1rem; + } + + .ingredient-row { + flex-direction: column; + } + + .ingredient-qty, + .ingredient-item { + flex: 1 0 auto; + width: 100%; + } + + .btn, + .btn-outline { + width: 100%; + margin: 0.25rem 0; + } + + .buttons { + display: flex; + flex-direction: column; + gap: 0.5rem; + } +} +.star-container { + display: inline-flex; + gap: 5px; + cursor: pointer; +} + +.fa-star { + font-size: 1.5rem; + color: #ccc; + transition: color 0.2s ease; +} + + .fa-star.rated { + color: #ffcc00; + } diff --git a/Aberwyn/wwwroot/css/site.css b/Aberwyn/wwwroot/css/site.css index 7e59509..fbd325c 100644 --- a/Aberwyn/wwwroot/css/site.css +++ b/Aberwyn/wwwroot/css/site.css @@ -429,3 +429,39 @@ body { background-color: #f0f0f0; } } +.btn-lewel { + background-color: #3399ff; /* blĂ„ accent */ + color: white; + border: none; + padding: 0.5rem 1.2rem; + border-radius: 6px; + font-size: 1rem; + font-weight: 500; + text-decoration: none; + display: inline-block; + cursor: pointer; + transition: background-color 0.2s ease; +} + + .btn-lewel:hover { + background-color: #2389e0; + } + +.btn-lewel-outline { + background-color: transparent; + color: #3399ff; + border: 2px solid #3399ff; + padding: 0.5rem 1.2rem; + border-radius: 6px; + font-size: 1rem; + font-weight: 500; + text-decoration: none; + display: inline-block; + cursor: pointer; + transition: background-color 0.2s ease, color 0.2s ease; +} + + .btn-lewel-outline:hover { + background-color: #e0f0ff; + color: #1F2C3C; + }