diff --git a/coffeecard/CoffeeCard.Library/CoffeeCard.Library.csproj b/coffeecard/CoffeeCard.Library/CoffeeCard.Library.csproj index 859831b4..5b085de9 100644 --- a/coffeecard/CoffeeCard.Library/CoffeeCard.Library.csproj +++ b/coffeecard/CoffeeCard.Library/CoffeeCard.Library.csproj @@ -1,4 +1,4 @@ - + net8.0 default @@ -9,7 +9,6 @@ - diff --git a/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.Designer.cs b/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.Designer.cs new file mode 100644 index 00000000..f95cd711 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.Designer.cs @@ -0,0 +1,652 @@ +// +using System; +using CoffeeCard.Library.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + [DbContext(typeof(CoffeeCardContext))] + [Migration("20240227174524_AddUserGroupIndexToUsers")] + partial class AddUserGroupIndexToUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dbo") + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Time") + .HasColumnType("datetime2"); + + b.Property("User_Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("User_Id"); + + b.ToTable("LoginAttempts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.MenuItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("MenuItems", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.MenuItemProduct", b => + { + b.Property("MenuItemId") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.HasKey("MenuItemId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("MenuItemProducts", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("BaristaInitials") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("PurchaseId"); + + b.ToTable("PosPurchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExperienceWorth") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("Visible") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("Products", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.HasKey("ProductId", "UserGroup"); + + b.ToTable("ProductUserGroups", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ShortName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortPriority") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Programmes", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("ExternalTransactionId") + .HasColumnType("nvarchar(450)"); + + b.Property("NumberOfTickets") + .HasColumnType("int"); + + b.Property("OrderId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Price") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("ProductName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PurchasedById") + .HasColumnType("int") + .HasColumnName("PurchasedBy_Id"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ExternalTransactionId"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchasedById"); + + b.ToTable("Purchases", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExpiryDate") + .HasColumnType("datetime2"); + + b.Property("LastSwipe") + .HasColumnType("datetime2"); + + b.Property("Preset") + .HasColumnType("int"); + + b.Property("SwipeCount") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Preset", "ExpiryDate"); + + b.ToTable("Statistics", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("OwnerId") + .HasColumnType("int") + .HasColumnName("Owner_Id"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("PurchaseId") + .HasColumnType("int") + .HasColumnName("Purchase_Id"); + + b.Property("UsedOnMenuItemId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UsedOnMenuItemId"); + + b.ToTable("Tickets", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUpdated") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Experience") + .HasColumnType("int"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PrivacyActivated") + .HasColumnType("bit"); + + b.Property("ProgrammeId") + .HasColumnType("int") + .HasColumnName("Programme_Id"); + + b.Property("Salt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserGroup") + .HasColumnType("int"); + + b.Property("UserState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("Name"); + + b.HasIndex("ProgrammeId"); + + b.HasIndex("UserGroup"); + + b.ToTable("Users", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateUsed") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("int") + .HasColumnName("Product_Id"); + + b.Property("PurchaseId") + .HasColumnType("int"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int") + .HasColumnName("User_Id"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("ProductId"); + + b.HasIndex("PurchaseId"); + + b.HasIndex("UserId"); + + b.ToTable("Vouchers", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.WebhookConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("SignatureKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("WebhookConfigurations", "dbo"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("LoginAttempts") + .HasForeignKey("User_Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.MenuItemProduct", b => + { + b.HasOne("CoffeeCard.Models.Entities.MenuItem", "MenuItem") + .WithMany("MenuItemProducts") + .HasForeignKey("MenuItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("MenuItemProducts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MenuItem"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.PosPurhase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Purchase"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.ProductUserGroup", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany("ProductUserGroup") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.User", "PurchasedBy") + .WithMany("Purchases") + .HasForeignKey("PurchasedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("PurchasedBy"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Statistic", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Statistics") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Ticket", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany("Tickets") + .HasForeignKey("PurchaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.MenuItem", "UsedOnMenuItem") + .WithMany() + .HasForeignKey("UsedOnMenuItemId"); + + b.Navigation("Owner"); + + b.Navigation("Purchase"); + + b.Navigation("UsedOnMenuItem"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Token", b => + { + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany("Tokens") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.HasOne("CoffeeCard.Models.Entities.Programme", "Programme") + .WithMany("Users") + .HasForeignKey("ProgrammeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Programme"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Voucher", b => + { + b.HasOne("CoffeeCard.Models.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CoffeeCard.Models.Entities.Purchase", "Purchase") + .WithMany() + .HasForeignKey("PurchaseId"); + + b.HasOne("CoffeeCard.Models.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Product"); + + b.Navigation("Purchase"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.MenuItem", b => + { + b.Navigation("MenuItemProducts"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Product", b => + { + b.Navigation("MenuItemProducts"); + + b.Navigation("ProductUserGroup"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Programme", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.Purchase", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("CoffeeCard.Models.Entities.User", b => + { + b.Navigation("LoginAttempts"); + + b.Navigation("Purchases"); + + b.Navigation("Statistics"); + + b.Navigation("Tickets"); + + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.cs b/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.cs new file mode 100644 index 00000000..3b18115e --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Migrations/20240227174524_AddUserGroupIndexToUsers.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CoffeeCard.Library.Migrations +{ + /// + public partial class AddUserGroupIndexToUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Users_UserGroup", + schema: "dbo", + table: "Users", + column: "UserGroup"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Users_UserGroup", + schema: "dbo", + table: "Users"); + } + } +} diff --git a/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs b/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs index a591c1c7..a683fc32 100644 --- a/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs +++ b/coffeecard/CoffeeCard.Library/Migrations/CoffeeCardContextModelSnapshot.cs @@ -18,10 +18,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("dbo") - .HasAnnotation("ProductVersion", "6.0.11") + .HasAnnotation("ProductVersion", "8.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("CoffeeCard.Models.Entities.LoginAttempt", b => { @@ -29,7 +29,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Time") .HasColumnType("datetime2"); @@ -50,7 +50,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Active") .HasColumnType("bit"); @@ -102,7 +102,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Description") .IsRequired() @@ -148,7 +148,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("FullName") .IsRequired() @@ -172,7 +172,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("DateCreated") .HasColumnType("datetime2"); @@ -229,7 +229,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ExpiryDate") .HasColumnType("datetime2"); @@ -262,7 +262,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("DateCreated") .HasColumnType("datetime2"); @@ -304,7 +304,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("TokenHash") .IsRequired() @@ -327,7 +327,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("DateCreated") .HasColumnType("datetime2"); @@ -379,6 +379,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ProgrammeId"); + b.HasIndex("UserGroup"); + b.ToTable("Users", "dbo"); }); @@ -388,7 +390,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("Code") .IsRequired() diff --git a/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs b/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs index e1aef2df..2e91b90b 100644 --- a/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs +++ b/coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs @@ -275,5 +275,19 @@ private static string EscapeName(string name) { return name.Trim('<', '>', '{', '}'); } + + public async Task UpdatePriviligedUserGroups(WebhookUpdateUserGroupRequest request) + { + await _context.Users + .Where(u => u.UserGroup != UserGroup.Customer) + .ExecuteUpdateAsync(u => u.SetProperty(u => u.UserGroup, UserGroup.Customer)); + + foreach (var item in request.PrivilegedUsers) + { + await _context.Users + .Where(u => u.Id == item.AccountId) + .ExecuteUpdateAsync(u => u.SetProperty(u => u.UserGroup, item.UserGroup)); + } + } } } \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Library/Services/v2/IAccountService.cs b/coffeecard/CoffeeCard.Library/Services/v2/IAccountService.cs index 42166a66..cd5d90fc 100644 --- a/coffeecard/CoffeeCard.Library/Services/v2/IAccountService.cs +++ b/coffeecard/CoffeeCard.Library/Services/v2/IAccountService.cs @@ -72,5 +72,10 @@ public interface IAccountService /// The page number /// The length of a page Task SearchUsers(String search, int pageNum, int pageLength); + + /// + /// Remove all existing priviliged user group assignments. Update users based on request contents + /// + Task UpdatePriviligedUserGroups(WebhookUpdateUserGroupRequest request); } } \ No newline at end of file diff --git a/coffeecard/CoffeeCard.MobilePay/Service/v2/MobilePayPaymentsService.cs b/coffeecard/CoffeeCard.MobilePay/Service/v2/MobilePayPaymentsService.cs index aaa98e0c..9d37d7a5 100644 --- a/coffeecard/CoffeeCard.MobilePay/Service/v2/MobilePayPaymentsService.cs +++ b/coffeecard/CoffeeCard.MobilePay/Service/v2/MobilePayPaymentsService.cs @@ -37,7 +37,7 @@ public async Task InitiatePayment(MobilePayPaymentReque Log.Information("Initiated Payment with MobilePay PaymentId {TransactionId} of {OrerAmount} Oerer kr.", response.PaymentId.ToString(), paymentRequest.Amount); return new MobilePayPaymentDetails(paymentRequest.OrderId.ToString(), response.MobilePayAppRedirectUri, - response.PaymentId.ToString(), null); + response.PaymentId.ToString()); } catch (ApiException e) { @@ -64,7 +64,7 @@ public async Task GetPayment(Guid paymentId) var response = await _paymentsApi.GetSinglePaymentAsync(paymentId, null); return new MobilePayPaymentDetails(response.Reference, response.RedirectUri, - response.PaymentId.ToString(), response.State.ToString()); + response.PaymentId.ToString()); } catch (ApiException e) { diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/FreeProductPaymentDetails.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/FreeProductPaymentDetails.cs index 159a15d0..82039568 100644 --- a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/FreeProductPaymentDetails.cs +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/FreeProductPaymentDetails.cs @@ -1,5 +1,3 @@ -using System.Runtime.Serialization; - namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase { /// @@ -11,7 +9,6 @@ namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase /// "orderId": "f5cb3e0f-3b9b-4f50-8c4f-a7450f300a5c" /// } /// - [KnownType(typeof(FreePurchasePaymentDetails))] public class FreePurchasePaymentDetails : PaymentDetails { /// diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/MobilePayPaymentDetails.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/MobilePayPaymentDetails.cs index 3fd4795d..5742561e 100644 --- a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/MobilePayPaymentDetails.cs +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/MobilePayPaymentDetails.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json; using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase { @@ -15,7 +13,6 @@ namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase /// "paymentId": "186d2b31-ff25-4414-9fd1-bfe9807fa8b7" /// } /// - [KnownType(typeof(MobilePayPaymentDetails))] public class MobilePayPaymentDetails : PaymentDetails { /// @@ -32,23 +29,15 @@ public class MobilePayPaymentDetails : PaymentDetails [Required] public string PaymentId { get; } - /// - /// MobilePay state - /// - /// Initiated - [JsonProperty(Required = Required.AllowNull)] - public string? State { get; } - /// /// Creates a new instance of /// - public MobilePayPaymentDetails(string orderId, string mobilePayAppRedirectUri, string paymentId, string? state) + public MobilePayPaymentDetails(string orderId, string mobilePayAppRedirectUri, string paymentId) { PaymentType = PaymentType.MobilePay; OrderId = orderId; MobilePayAppRedirectUri = mobilePayAppRedirectUri; PaymentId = paymentId; - State = state; } } -} \ No newline at end of file +} diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentDetails.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentDetails.cs index 1b72271e..33d4ede6 100644 --- a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentDetails.cs +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Purchase/PaymentDetails.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; -using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace CoffeeCard.Models.DataTransferObjects.v2.Purchase { - [KnownType(typeof(MobilePayPaymentDetails))] - [KnownType(typeof(FreePurchasePaymentDetails))] + [JsonPolymorphic(TypeDiscriminatorPropertyName = "discriminator")] + [JsonDerivedType(typeof(MobilePayPaymentDetails), typeDiscriminator: "MobilePayPaymentDetails")] + [JsonDerivedType(typeof(FreePurchasePaymentDetails), typeDiscriminator: "FreePurchasePaymentDetails")] public abstract class PaymentDetails { /// diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/AccountUserGroup.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/AccountUserGroup.cs new file mode 100644 index 00000000..d465b08b --- /dev/null +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/AccountUserGroup.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; + +namespace CoffeeCard.Models.Entities; + +/// +/// Represents an account user group update +/// +public class AccountUserGroup +{ + /// + /// The account id + /// + /// Account id + /// 1 + [Required] + public int AccountId { get; set; } + + /// + /// The user group + /// + /// User group + /// Barista + [Required] + public UserGroup UserGroup { get; set; } +} diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/UserSearchResponse.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/UserSearchResponse.cs index 311ae222..cfd2f427 100644 --- a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/UserSearchResponse.cs +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/UserSearchResponse.cs @@ -32,5 +32,5 @@ public class UserSearchResponse /// ] /// [Required] - public IEnumerable Users; + public IEnumerable Users { get; set; } } \ No newline at end of file diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/WebhookUpdateUserGroupRequest.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/WebhookUpdateUserGroupRequest.cs new file mode 100644 index 00000000..ae76c87d --- /dev/null +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/User/WebhookUpdateUserGroupRequest.cs @@ -0,0 +1,19 @@ +using CoffeeCard.Models.Entities; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace CoffeeCard.Models.DataTransferObjects.v2.User +{ + /// + /// Represents a request to update user groups in bulk + /// + public class WebhookUpdateUserGroupRequest + { + /// + /// List of accounts and their new user groups + /// + + [Required] + public IEnumerable PrivilegedUsers { get; set; } + } +} diff --git a/coffeecard/CoffeeCard.Models/Entities/User.cs b/coffeecard/CoffeeCard.Models/Entities/User.cs index 0bdeb613..fe493d58 100644 --- a/coffeecard/CoffeeCard.Models/Entities/User.cs +++ b/coffeecard/CoffeeCard.Models/Entities/User.cs @@ -9,6 +9,7 @@ namespace CoffeeCard.Models.Entities { [Index(nameof(Email))] [Index(nameof(Name))] + [Index(nameof(UserGroup))] public class User { public int Id { get; set; } diff --git a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs index 614bc2f0..b52279fa 100644 --- a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs +++ b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/PurchaseServiceTests.cs @@ -173,7 +173,7 @@ public async Task InitiatePurchasePaymentTypeMobilePay() var orderId = Guid.NewGuid().ToString(); var mpDeepLink = "mobilepay://merchant_payments?payment_id=186d2b31-ff25-4414-9fd1-bfe9807fa8b7"; mobilePayService.Setup(mps => mps.InitiatePayment(It.IsAny())) - .ReturnsAsync(new MobilePayPaymentDetails(orderId, mpDeepLink, mobilepayPaymentId, "Initiated")); + .ReturnsAsync(new MobilePayPaymentDetails(orderId, mpDeepLink, mobilepayPaymentId)); // Act var result = await purchaseService.InitiatePurchase(request, user); diff --git a/coffeecard/CoffeeCard.WebApi/CoffeeCard.WebApi.csproj b/coffeecard/CoffeeCard.WebApi/CoffeeCard.WebApi.csproj index 5eebbc7e..fb461de5 100644 --- a/coffeecard/CoffeeCard.WebApi/CoffeeCard.WebApi.csproj +++ b/coffeecard/CoffeeCard.WebApi/CoffeeCard.WebApi.csproj @@ -34,7 +34,6 @@ - diff --git a/coffeecard/CoffeeCard.WebApi/Controllers/v2/WebhooksController.cs b/coffeecard/CoffeeCard.WebApi/Controllers/v2/WebhooksController.cs new file mode 100644 index 00000000..94750f62 --- /dev/null +++ b/coffeecard/CoffeeCard.WebApi/Controllers/v2/WebhooksController.cs @@ -0,0 +1,48 @@ +using CoffeeCard.Library.Services.v2; +using CoffeeCard.Models.DataTransferObjects.v2.User; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace CoffeeCard.WebApi.Controllers.v2 +{ + /// + /// Controller for creating and managing user accounts + /// + [ApiController] + [ApiVersion("2")] + [Route("api/v{version:apiVersion}/webhooks")] + [Authorize(AuthenticationSchemes = "apikey")] + public class WebhooksController : ControllerBase + { + private readonly IAccountService _accountService; + + /// + /// Initializes a new instance of the class. + /// + public WebhooksController(IAccountService accountService) + { + _accountService = accountService; + } + + /// + /// Update user groups in bulk + /// + /// The request containing the new user groups + /// No content + /// The user groups were updated + /// Bad request. See explanation + /// Invalid credentials + [HttpPut("accounts/user-group")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task UpdateUserGroupsAsync([FromBody][Required] WebhookUpdateUserGroupRequest request) + { + await _accountService.UpdatePriviligedUserGroups(request); + return NoContent(); + } + } +} diff --git a/coffeecard/CoffeeCard.WebApi/Dockerfile b/coffeecard/CoffeeCard.WebApi/Dockerfile index 945d0878..df929dd3 100644 --- a/coffeecard/CoffeeCard.WebApi/Dockerfile +++ b/coffeecard/CoffeeCard.WebApi/Dockerfile @@ -2,8 +2,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 80 -EXPOSE 443 +EXPOSE 8080 +EXPOSE 8001 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src diff --git a/coffeecard/CoffeeCard.WebApi/Startup.cs b/coffeecard/CoffeeCard.WebApi/Startup.cs index ad1cffa8..6a960e6a 100644 --- a/coffeecard/CoffeeCard.WebApi/Startup.cs +++ b/coffeecard/CoffeeCard.WebApi/Startup.cs @@ -1,5 +1,7 @@ using System; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using AspNetCore.Authentication.ApiKey; using CoffeeCard.Common.Configuration; @@ -103,11 +105,11 @@ public void ConfigureServices(IServiceCollection services) options.Filters.Add(new ApiExceptionFilter()); options.Filters.Add(new ReadableBodyFilter()); }) - // Setup Json Serializing - .AddNewtonsoftJson(options => + .AddJsonOptions(options => { - options.SerializerSettings.Converters.Add(new StringEnumConverter()); - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.Never; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }); services.AddCors(options => options.AddDefaultPolicy(builder => @@ -198,6 +200,7 @@ private static void GenerateOpenApiDocument(IServiceCollection services) // Add an OpenApi document per API version services.AddOpenApiDocument(config => { + config.DefaultResponseReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull; config.Title = apiVersion.GroupName; config.Version = apiVersion.ApiVersion.ToString(); config.DocumentName = apiVersion.GroupName; diff --git a/coffeecard/docker-compose.yml b/coffeecard/docker-compose.yml index ab267636..b94bab9f 100644 --- a/coffeecard/docker-compose.yml +++ b/coffeecard/docker-compose.yml @@ -19,8 +19,14 @@ services: dockerfile: CoffeeCard.WebApi/Dockerfile depends_on: [ mssql ] ports: - - 8080:80 - - 8001:443 + - 8080:8080 + - 8001:8001 + environment: + - ASPNETCORE_ENVIRONMENT=Development + # - ASPNETCORE_URLS=https://+:443;http://+:80 + - ASPNETCORE_HTTPS_PORTS=8001 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/CoffeeCard.WebApi.pfx + - ASPNETCORE_Kestrel__Certificates__Default__Password=crypticpassword volumes: - - ./CoffeeCard.WebApi/appsettings.json:/app/appsettings.json + - ./CoffeeCard.WebApi/appsettings.json:/app/appsettings.json:z - ~/.aspnet/https:/https:ro