From 9e55b459fce6dc54a746a58099d75b36ebb44b1d Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 12 Jul 2021 03:07:16 -0500 Subject: [PATCH] Ported some code from Blazor version of project. --- .../PublicApiSettingsController.cs | 32 ++ .../UserConfigurationsController.cs | 82 +++++ .../Controllers/WeatherForecastController.cs | 3 +- MultiShop/server/Data/ApplicationDbContext.cs | 30 ++ ... 20210712080053_InitialCreate.Designer.cs} | 279 +++++++++++++----- ...ema.cs => 20210712080053_InitialCreate.cs} | 105 ++++++- .../ApplicationDbContextModelSnapshot.cs | 275 ++++++++++++----- .../server/Models/ApplicationPreferences.cs | 15 + MultiShop/server/Models/ApplicationUser.cs | 10 + MultiShop/server/Models/ResultsPreferences.cs | 34 +++ MultiShop/server/Models/SearchOutline.cs | 125 ++++++++ MultiShop/server/MultiShop.csproj | 3 + MultiShop/server/appsettings.json | 13 +- .../server/options/IdentificationOptions.cs | 8 + 14 files changed, 873 insertions(+), 141 deletions(-) create mode 100644 MultiShop/server/Controllers/PublicApiSettingsController.cs create mode 100644 MultiShop/server/Controllers/UserConfigurationsController.cs rename MultiShop/server/Data/Migrations/{00000000000000_CreateIdentitySchema.Designer.cs => 20210712080053_InitialCreate.Designer.cs} (71%) rename MultiShop/server/Data/Migrations/{00000000000000_CreateIdentitySchema.cs => 20210712080053_InitialCreate.cs} (71%) create mode 100644 MultiShop/server/Models/ApplicationPreferences.cs create mode 100644 MultiShop/server/Models/ResultsPreferences.cs create mode 100644 MultiShop/server/Models/SearchOutline.cs create mode 100644 MultiShop/server/options/IdentificationOptions.cs diff --git a/MultiShop/server/Controllers/PublicApiSettingsController.cs b/MultiShop/server/Controllers/PublicApiSettingsController.cs new file mode 100644 index 0000000..44e49ed --- /dev/null +++ b/MultiShop/server/Controllers/PublicApiSettingsController.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using MultiShop.Options; + +namespace MultiShop.Server.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class PublicApiSettingsController : ControllerBase + { + private IConfiguration configuration; + public PublicApiSettingsController(IConfiguration configuration) + { + this.configuration = configuration; + } + + [HttpGet] + public IActionResult Get() { + return Ok(BuildPublicApiSettings()); + } + + private IReadOnlyDictionary BuildPublicApiSettings() { + IdentificationOptions identificationOptions = configuration.GetSection(IdentificationOptions.Identification).Get(); + return new Dictionary() { + // Build dictionary containing options client should be aware of. + {"RegistrationEnabled", identificationOptions.RegistrationEnabled.ToString()} + }; + } + } +} \ No newline at end of file diff --git a/MultiShop/server/Controllers/UserConfigurationsController.cs b/MultiShop/server/Controllers/UserConfigurationsController.cs new file mode 100644 index 0000000..637fbd0 --- /dev/null +++ b/MultiShop/server/Controllers/UserConfigurationsController.cs @@ -0,0 +1,82 @@ +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using MultiShop.Data; +using MultiShop.Models; +using MultiShop.Shared.Models; + +namespace MultiShop.Server.Controllers +{ + + [ApiController] + [Authorize] + [Route("api/[controller]")] + public class UserConfigurationsController : ControllerBase + { + private ILogger logger; + private UserManager userManager; + private ApplicationDbContext dbContext; + public UserConfigurationsController(UserManager userManager, ApplicationDbContext dbContext, ILogger logger) + { + this.userManager = userManager; + this.dbContext = dbContext; + this.logger = logger; + } + + [HttpGet] + public async Task GetSearchOutline() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + return Ok(userModel.SearchOutline); + } + + [HttpGet] + public async Task GetResultsPreferences() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + return Ok(userModel.ResultsPreferences); + } + + [HttpGet] + public async Task GetApplicationPreferences() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + logger.LogInformation(JsonSerializer.Serialize(userModel.ApplicationPreferences)); + return Ok(userModel.ApplicationPreferences); + } + + [HttpPut] + public async Task PutSearchOutline(SearchOutline searchOutline) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + if (userModel.SearchOutline.Id != searchOutline.Id || userModel.Id != searchOutline.ApplicationUserId) { + return BadRequest(); + } + dbContext.Entry(userModel.SearchOutline).CurrentValues.SetValues(searchOutline); + await userManager.UpdateAsync(userModel); + return NoContent(); + } + + [HttpPut] + public async Task PutResultsPreferences(ResultsPreferences resultsPreferences) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + if (userModel.ResultsPreferences.Id != resultsPreferences.Id || userModel.Id != resultsPreferences.ApplicationUserId) { + return BadRequest(); + } + dbContext.Entry(userModel.ResultsPreferences).CurrentValues.SetValues(resultsPreferences); + await userManager.UpdateAsync(userModel); + return NoContent(); + } + + [HttpPut] + public async Task PutApplicationPreferences(ApplicationPreferences applicationPreferences) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + logger.LogInformation(JsonSerializer.Serialize(applicationPreferences)); + if (userModel.ApplicationPreferences.Id != applicationPreferences.Id || userModel.Id != applicationPreferences.ApplicationUserId) { + return BadRequest(); + } + dbContext.Entry(userModel.ApplicationPreferences).CurrentValues.SetValues(applicationPreferences); + await userManager.UpdateAsync(userModel); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/MultiShop/server/Controllers/WeatherForecastController.cs b/MultiShop/server/Controllers/WeatherForecastController.cs index 0975225..6a67d0f 100644 --- a/MultiShop/server/Controllers/WeatherForecastController.cs +++ b/MultiShop/server/Controllers/WeatherForecastController.cs @@ -8,9 +8,10 @@ using Microsoft.Extensions.Logging; namespace MultiShop.Controllers { + // TODO: Create new shop search controller. [Authorize] [ApiController] - [Route("[controller]")] + [Route("api/[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] diff --git a/MultiShop/server/Data/ApplicationDbContext.cs b/MultiShop/server/Data/ApplicationDbContext.cs index 0673b42..930c73c 100644 --- a/MultiShop/server/Data/ApplicationDbContext.cs +++ b/MultiShop/server/Data/ApplicationDbContext.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.ChangeTracking; namespace MultiShop.Data { @@ -17,5 +19,33 @@ namespace MultiShop.Data IOptions operationalStoreOptions) : base(options, operationalStoreOptions) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(e => e.Order) + .HasConversion( + v => JsonSerializer.Serialize(v, null), + v => JsonSerializer.Deserialize>(v, null), + new ValueComparer>( + (a, b) => a.SequenceEqual(b), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => (IList) c.ToList() + ) + ); + + modelBuilder.Entity() + .Property(e => e.ShopStates) + .HasConversion( + v => JsonSerializer.Serialize(v, null), + v => JsonSerializer.Deserialize(v, null), + new ValueComparer( + (a, b) => a.Equals(b), + c => c.GetHashCode(), + c => c.Clone() + ) + ); + } } } diff --git a/MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/MultiShop/server/Data/Migrations/20210712080053_InitialCreate.Designer.cs similarity index 71% rename from MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs rename to MultiShop/server/Data/Migrations/20210712080053_InitialCreate.Designer.cs index 5cbe393..4125c92 100644 --- a/MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs +++ b/MultiShop/server/Data/Migrations/20210712080053_InitialCreate.Designer.cs @@ -1,86 +1,22 @@ // using System; -using MultiShop.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MultiShop.Data; namespace MultiShop.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("00000000000000_CreateIdentitySchema")] - partial class CreateIdentitySchema + [Migration("20210712080053_InitialCreate")] + partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); - - modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); + .HasAnnotation("ProductVersion", "5.0.5"); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { @@ -317,6 +253,180 @@ namespace MultiShop.Data.Migrations b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("MultiShop.Models.ResultsPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Order") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ResultsPreferences"); + }); + + modelBuilder.Entity("MultiShop.Models.SearchOutline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("EnableMaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("EnableUpperPrice") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownPurchaseCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownRatingCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownShipping") + .HasColumnType("INTEGER"); + + b.Property("KeepUnrated") + .HasColumnType("INTEGER"); + + b.Property("LowerPrice") + .HasColumnType("INTEGER"); + + b.Property("MaxResults") + .HasColumnType("INTEGER"); + + b.Property("MaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("MinPurchases") + .HasColumnType("INTEGER"); + + b.Property("MinRating") + .HasColumnType("REAL"); + + b.Property("MinReviews") + .HasColumnType("INTEGER"); + + b.Property("ShopStates") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpperPrice") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("SearchOutline"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("CacheCommonSearches") + .HasColumnType("INTEGER"); + + b.Property("DarkMode") + .HasColumnType("INTEGER"); + + b.Property("EnableSearchHistory") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ApplicationPreferences"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -367,6 +477,39 @@ namespace MultiShop.Data.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("MultiShop.Models.ResultsPreferences", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("ResultsPreferences") + .HasForeignKey("MultiShop.Models.ResultsPreferences", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Models.SearchOutline", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("SearchOutline") + .HasForeignKey("MultiShop.Models.SearchOutline", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationPreferences", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("ApplicationPreferences") + .HasForeignKey("MultiShop.Shared.Models.ApplicationPreferences", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => + { + b.Navigation("ApplicationPreferences") + .IsRequired(); + + b.Navigation("ResultsPreferences") + .IsRequired(); + + b.Navigation("SearchOutline") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/MultiShop/server/Data/Migrations/20210712080053_InitialCreate.cs similarity index 71% rename from MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.cs rename to MultiShop/server/Data/Migrations/20210712080053_InitialCreate.cs index 49f585d..6e8f692 100644 --- a/MultiShop/server/Data/Migrations/00000000000000_CreateIdentitySchema.cs +++ b/MultiShop/server/Data/Migrations/20210712080053_InitialCreate.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace MultiShop.Data.Migrations { - public partial class CreateIdentitySchema : Migration + public partial class InitialCreate : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -106,6 +106,28 @@ namespace MultiShop.Data.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ApplicationPreferences", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + DarkMode = table.Column(type: "INTEGER", nullable: false), + CacheCommonSearches = table.Column(type: "INTEGER", nullable: false), + EnableSearchHistory = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationPreferences", x => x.Id); + table.ForeignKey( + name: "FK_ApplicationPreferences_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "AspNetUserClaims", columns: table => new @@ -191,6 +213,66 @@ namespace MultiShop.Data.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ResultsPreferences", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + Order = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ResultsPreferences", x => x.Id); + table.ForeignKey( + name: "FK_ResultsPreferences_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SearchOutline", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + Currency = table.Column(type: "INTEGER", nullable: false), + MaxResults = table.Column(type: "INTEGER", nullable: false), + MinRating = table.Column(type: "REAL", nullable: false), + KeepUnrated = table.Column(type: "INTEGER", nullable: false), + EnableUpperPrice = table.Column(type: "INTEGER", nullable: false), + UpperPrice = table.Column(type: "INTEGER", nullable: false), + LowerPrice = table.Column(type: "INTEGER", nullable: false), + MinPurchases = table.Column(type: "INTEGER", nullable: false), + KeepUnknownPurchaseCount = table.Column(type: "INTEGER", nullable: false), + MinReviews = table.Column(type: "INTEGER", nullable: false), + KeepUnknownRatingCount = table.Column(type: "INTEGER", nullable: false), + EnableMaxShippingFee = table.Column(type: "INTEGER", nullable: false), + MaxShippingFee = table.Column(type: "INTEGER", nullable: false), + KeepUnknownShipping = table.Column(type: "INTEGER", nullable: false), + ShopStates = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SearchOutline", x => x.Id); + table.ForeignKey( + name: "FK_SearchOutline_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApplicationPreferences_ApplicationUserId", + table: "ApplicationPreferences", + column: "ApplicationUserId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", @@ -253,10 +335,25 @@ namespace MultiShop.Data.Migrations name: "IX_PersistedGrants_SubjectId_SessionId_Type", table: "PersistedGrants", columns: new[] { "SubjectId", "SessionId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_ResultsPreferences_ApplicationUserId", + table: "ResultsPreferences", + column: "ApplicationUserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SearchOutline_ApplicationUserId", + table: "SearchOutline", + column: "ApplicationUserId", + unique: true); } protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "ApplicationPreferences"); + migrationBuilder.DropTable( name: "AspNetRoleClaims"); @@ -278,6 +375,12 @@ namespace MultiShop.Data.Migrations migrationBuilder.DropTable( name: "PersistedGrants"); + migrationBuilder.DropTable( + name: "ResultsPreferences"); + + migrationBuilder.DropTable( + name: "SearchOutline"); + migrationBuilder.DropTable( name: "AspNetRoles"); diff --git a/MultiShop/server/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/MultiShop/server/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 9fbbbcd..0a69260 100644 --- a/MultiShop/server/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/MultiShop/server/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,9 +1,9 @@ // using System; -using MultiShop.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MultiShop.Data; namespace MultiShop.Data.Migrations { @@ -14,71 +14,7 @@ namespace MultiShop.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); - - modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); + .HasAnnotation("ProductVersion", "5.0.5"); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { @@ -315,6 +251,180 @@ namespace MultiShop.Data.Migrations b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("MultiShop.Models.ResultsPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Order") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ResultsPreferences"); + }); + + modelBuilder.Entity("MultiShop.Models.SearchOutline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("EnableMaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("EnableUpperPrice") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownPurchaseCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownRatingCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownShipping") + .HasColumnType("INTEGER"); + + b.Property("KeepUnrated") + .HasColumnType("INTEGER"); + + b.Property("LowerPrice") + .HasColumnType("INTEGER"); + + b.Property("MaxResults") + .HasColumnType("INTEGER"); + + b.Property("MaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("MinPurchases") + .HasColumnType("INTEGER"); + + b.Property("MinRating") + .HasColumnType("REAL"); + + b.Property("MinReviews") + .HasColumnType("INTEGER"); + + b.Property("ShopStates") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpperPrice") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("SearchOutline"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("CacheCommonSearches") + .HasColumnType("INTEGER"); + + b.Property("DarkMode") + .HasColumnType("INTEGER"); + + b.Property("EnableSearchHistory") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ApplicationPreferences"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -365,6 +475,39 @@ namespace MultiShop.Data.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("MultiShop.Models.ResultsPreferences", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("ResultsPreferences") + .HasForeignKey("MultiShop.Models.ResultsPreferences", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Models.SearchOutline", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("SearchOutline") + .HasForeignKey("MultiShop.Models.SearchOutline", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationPreferences", b => + { + b.HasOne("MultiShop.Models.ApplicationUser", null) + .WithOne("ApplicationPreferences") + .HasForeignKey("MultiShop.Shared.Models.ApplicationPreferences", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Models.ApplicationUser", b => + { + b.Navigation("ApplicationPreferences") + .IsRequired(); + + b.Navigation("ResultsPreferences") + .IsRequired(); + + b.Navigation("SearchOutline") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/MultiShop/server/Models/ApplicationPreferences.cs b/MultiShop/server/Models/ApplicationPreferences.cs new file mode 100644 index 0000000..90a7c0b --- /dev/null +++ b/MultiShop/server/Models/ApplicationPreferences.cs @@ -0,0 +1,15 @@ +namespace MultiShop.Shared.Models +{ + public class ApplicationPreferences + { + public int Id { get; set; } + + public string ApplicationUserId { get; set; } + + public bool DarkMode { get; set; } + + public bool CacheCommonSearches { get; set; } = true; + + public bool EnableSearchHistory { get; set; } = true; + } +} \ No newline at end of file diff --git a/MultiShop/server/Models/ApplicationUser.cs b/MultiShop/server/Models/ApplicationUser.cs index 7d80901..3f6535a 100644 --- a/MultiShop/server/Models/ApplicationUser.cs +++ b/MultiShop/server/Models/ApplicationUser.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Identity; +using MultiShop.Shared.Models; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; @@ -8,5 +10,13 @@ namespace MultiShop.Models { public class ApplicationUser : IdentityUser { + [Required] + public virtual SearchOutline SearchOutline { get; private set; } = new SearchOutline(); + + [Required] + public virtual ResultsPreferences ResultsPreferences { get; private set; } = new ResultsPreferences(); + + [Required] + public virtual ApplicationPreferences ApplicationPreferences {get; private set; } = new ApplicationPreferences(); } } diff --git a/MultiShop/server/Models/ResultsPreferences.cs b/MultiShop/server/Models/ResultsPreferences.cs new file mode 100644 index 0000000..c932821 --- /dev/null +++ b/MultiShop/server/Models/ResultsPreferences.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text.Json; + +namespace MultiShop.Models +{ + public class ResultsPreferences + { + public int Id { get; set; } + public string ApplicationUserId { get; set; } + + [Required] + public IList Order { get; set; } + + public ResultsPreferences() + { + Order = new List(Enum.GetValues().Length); + foreach (Category category in Enum.GetValues()) + { + Order.Add(category); + } + } + + public enum Category + { + RatingPriceRatio, + Reviews, + Purchases, + Price, + } + } +} \ No newline at end of file diff --git a/MultiShop/server/Models/SearchOutline.cs b/MultiShop/server/Models/SearchOutline.cs new file mode 100644 index 0000000..28e0378 --- /dev/null +++ b/MultiShop/server/Models/SearchOutline.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using MultiShop.Shop.Framework; + +namespace MultiShop.Models +{ + public class SearchOutline + { + public int Id { get; set; } + public string ApplicationUserId { get; set; } + + public Currency Currency { get; set; } = Currency.CAD; + public int MaxResults { get; set; } = 100; + public float MinRating { get; set; } = 0.8f; + public bool KeepUnrated { get; set; } = true; + public bool EnableUpperPrice { get; set; } = false; + private int _upperPrice; + + public int UpperPrice + { + get + { + return _upperPrice; + } + set + { + if (EnableUpperPrice) _upperPrice = value; + } + } + public int LowerPrice { get; set; } + public int MinPurchases { get; set; } + public bool KeepUnknownPurchaseCount { get; set; } = true; + public int MinReviews { get; set; } + public bool KeepUnknownRatingCount { get; set; } = true; + public bool EnableMaxShippingFee { get; set; } + private int _maxShippingFee; + + public int MaxShippingFee + { + get + { + return _maxShippingFee; + } + set + { + if (EnableMaxShippingFee) _maxShippingFee = value; + } + } + public bool KeepUnknownShipping { get; set; } = true; + + [Required] + public ShopToggler ShopStates { get; set; } = new ShopToggler(); + + public sealed class ShopToggler : HashSet + { + public int TotalShops { get; set; } + public bool this[string name] { + get { + return !this.Contains(name); + } + set { + if (value == false && TotalShops - Count <= 1) return; + if (value) + { + this.Remove(name); + } + else + { + this.Add(name); + } + } + } + + public ShopToggler Clone() { + ShopToggler clone = new ShopToggler(); + clone.Union(this); + return clone; + } + + public bool IsShopToggleable(string shop) + { + return (!Contains(shop) && TotalShops - Count > 1) || Contains(shop); + } + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + SearchOutline other = (SearchOutline) obj; + return + Id == other.Id && + Currency == other.Currency && + MaxResults == other.MaxResults && + MinRating == other.MinRating && + KeepUnrated == other.KeepUnrated && + EnableUpperPrice == other.EnableUpperPrice && + UpperPrice == other.UpperPrice && + LowerPrice == other.LowerPrice && + MinPurchases == other.MinPurchases && + KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount && + MinReviews == other.MinReviews && + KeepUnknownRatingCount == other.KeepUnknownRatingCount && + EnableMaxShippingFee == other.EnableMaxShippingFee && + MaxShippingFee == other.MaxShippingFee && + KeepUnknownShipping == other.KeepUnknownShipping && + ShopStates.Equals(other.ShopStates); + } + + public override int GetHashCode() + { + return Id; + } + + public SearchOutline DeepCopy() { + SearchOutline profile = (SearchOutline)MemberwiseClone(); + profile.ShopStates = ShopStates.Clone(); + return profile; + } + } +} \ No newline at end of file diff --git a/MultiShop/server/MultiShop.csproj b/MultiShop/server/MultiShop.csproj index 5bf1ab7..5619b39 100644 --- a/MultiShop/server/MultiShop.csproj +++ b/MultiShop/server/MultiShop.csproj @@ -32,6 +32,9 @@ + + + diff --git a/MultiShop/server/appsettings.json b/MultiShop/server/appsettings.json index b2098f8..948c223 100644 --- a/MultiShop/server/appsettings.json +++ b/MultiShop/server/appsettings.json @@ -3,11 +3,14 @@ "DefaultConnection": "DataSource=app.db;Cache=Shared" }, "Logging": { - "LogLevel": { + "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" - } - }, -"AllowedHosts": "*" -} + } + }, + "Identification": { + "RegistrationEnabled": true + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/MultiShop/server/options/IdentificationOptions.cs b/MultiShop/server/options/IdentificationOptions.cs new file mode 100644 index 0000000..c30fe26 --- /dev/null +++ b/MultiShop/server/options/IdentificationOptions.cs @@ -0,0 +1,8 @@ +namespace MultiShop.Options +{ + public class IdentificationOptions + { + public const string Identification = "Identification"; + public bool RegistrationEnabled { get; set; } + } +} \ No newline at end of file