From c6b8ca523bc6fd0bd003b4241f2a79ca59afd992 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 17 Aug 2021 02:59:01 -0500 Subject: [PATCH] Basic search outline config UI implemented. --- .../Props.Shop/Adafruit/AdafruitShop.cs | 7 +- Props-Modules/Props.Shop/Framework/Filters.cs | 12 +- Props/Controllers/SearchController.cs | 26 +- Props/Controllers/SearchOutlineController.cs | 202 ++++++- Props/Controllers/UserController.cs | 14 + Props/Data/ApplicationDbContext.cs | 10 +- ... 20210817042955_InitialCreate.Designer.cs} | 41 +- ...ate.cs => 20210817042955_InitialCreate.cs} | 135 ++--- .../ApplicationDbContextModelSnapshot.cs | 39 +- Props/Models/Search/SearchOutline.cs | 42 +- Props/Models/User/ResultsPreferences.cs | 1 - Props/Models/User/SearchOutlinePreferences.cs | 13 +- Props/Options/MetricsOptions.cs | 9 + Props/Pages/Search.cshtml | 515 ++++++++++-------- Props/Pages/Search.cshtml.cs | 32 +- Props/Services/Modules/IMetricsManager.cs | 2 +- Props/Services/Modules/LiveMetricsManager.cs | 45 +- Props/Services/Modules/LiveSearchManager.cs | 4 +- Props/Startup.cs | 8 - Props/appsettings.json | 4 + Props/assets/js/specific/search.js | 260 ++++++++- Props/assets/styles/site.scss | 132 ++++- Props/package-lock.json | 10 + Props/package.json | 2 + Props/textual/search.json | 2 +- 25 files changed, 1047 insertions(+), 520 deletions(-) create mode 100644 Props/Controllers/UserController.cs rename Props/Data/Migrations/{20210809194646_InitialCreate.Designer.cs => 20210817042955_InitialCreate.Designer.cs} (94%) rename Props/Data/Migrations/{20210809194646_InitialCreate.cs => 20210817042955_InitialCreate.cs} (87%) create mode 100644 Props/Options/MetricsOptions.cs diff --git a/Props-Modules/Props.Shop/Adafruit/AdafruitShop.cs b/Props-Modules/Props.Shop/Adafruit/AdafruitShop.cs index ef4e639..5bae47a 100644 --- a/Props-Modules/Props.Shop/Adafruit/AdafruitShop.cs +++ b/Props-Modules/Props.Shop/Adafruit/AdafruitShop.cs @@ -123,8 +123,13 @@ namespace Props.Shop.Adafruit if (workspaceDir != null) { logger.LogDebug("Saving data in \"{0}\"...", workspaceDir); + string configurationPath = Path.Combine(workspaceDir, Configuration.FILE_NAME); + File.Delete(configurationPath); await File.WriteAllTextAsync(Path.Combine(workspaceDir, Configuration.FILE_NAME), JsonSerializer.Serialize(configuration)); - using (Stream fileStream = File.OpenWrite(Path.Combine(workspaceDir, ProductListingCacheData.FILE_NAME))) + + string productListingCachePath = Path.Combine(workspaceDir, ProductListingCacheData.FILE_NAME); + File.Delete(productListingCachePath); + using (Stream fileStream = File.OpenWrite(productListingCachePath)) { await JsonSerializer.SerializeAsync(fileStream, new ProductListingCacheData(await searchManager.ProductListingManager.ProductListings)); } diff --git a/Props-Modules/Props.Shop/Framework/Filters.cs b/Props-Modules/Props.Shop/Framework/Filters.cs index 9d73e97..c9387d7 100644 --- a/Props-Modules/Props.Shop/Framework/Filters.cs +++ b/Props-Modules/Props.Shop/Framework/Filters.cs @@ -5,12 +5,12 @@ namespace Props.Shop.Framework public class Filters { public Currency Currency { get; set; } = Currency.CAD; - private float minRatingNormalized; + private float minRatingNormalized = 0.8f; public int MinRating { get { - return (int)(minRatingNormalized * 100); + return (int)(minRatingNormalized * 100f); } set { @@ -38,7 +38,7 @@ namespace Props.Shop.Framework public bool KeepUnknownPurchaseCount { get; set; } = true; public int MinReviews { get; set; } public bool KeepUnknownReviewCount { get; set; } = true; - public bool EnableMaxShippingFee { get; set; } + public bool EnableMaxShipping { get; set; } private int maxShippingFee; public int MaxShippingFee @@ -49,7 +49,7 @@ namespace Props.Shop.Framework } set { - if (EnableMaxShippingFee) maxShippingFee = value; + if (EnableMaxShipping) maxShippingFee = value; } } public bool KeepUnknownShipping { get; set; } = true; @@ -72,7 +72,7 @@ namespace Props.Shop.Framework KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount && MinReviews == other.MinReviews && KeepUnknownReviewCount == other.KeepUnknownReviewCount && - EnableMaxShippingFee == other.EnableMaxShippingFee && + EnableMaxShipping == other.EnableMaxShipping && MaxShippingFee == other.MaxShippingFee && KeepUnknownShipping == other.KeepUnknownShipping; @@ -96,7 +96,7 @@ namespace Props.Shop.Framework public bool Validate(ProductListing listing) { - if (listing.Shipping == null && !KeepUnknownShipping || (EnableMaxShippingFee && listing.Shipping > MaxShippingFee)) return false; + if (listing.Shipping == null && !KeepUnknownShipping || (EnableMaxShipping && listing.Shipping > MaxShippingFee)) return false; float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; if (!(listing.LowerPrice + shippingDifference >= LowerPrice && (!EnableUpperPrice || listing.UpperPrice + shippingDifference <= UpperPrice))) return false; if ((listing.Rating == null && !KeepUnrated) && MinRating > (listing.Rating == null ? 0 : listing.Rating)) return false; diff --git a/Props/Controllers/SearchController.cs b/Props/Controllers/SearchController.cs index 639f5b2..b2ed0dd 100644 --- a/Props/Controllers/SearchController.cs +++ b/Props/Controllers/SearchController.cs @@ -1,24 +1,38 @@ +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Props.Models.Search; using Props.Services.Modules; +using Props.Shop.Framework; namespace Props.Controllers { public class SearchController : ApiControllerBase { private SearchOutline defaultOutline = new SearchOutline(); - IShopManager shopManager; + ISearchManager searchManager; - public SearchController(IShopManager shopManager) + public SearchController(ISearchManager searchManager) { - this.shopManager = shopManager; + this.searchManager = searchManager; } [HttpGet] - [Route("Available")] - public IActionResult GetAvailableShops() + [Route("AvailableShops")] + public async Task GetAvailableShops() { - return Ok(shopManager.GetAllShopNames()); + return Ok(await searchManager.ShopManager.GetAllShopNames()); + } + + [HttpGet] + [Route("SearchShops/{search}/")] + public async Task GetSearch(string searchQuery, [FromQuery] SearchOutline searchOutline) + { + if (searchQuery == null) return BadRequest(); + + return Ok(await searchManager.Search(searchQuery, searchOutline)); } } } \ No newline at end of file diff --git a/Props/Controllers/SearchOutlineController.cs b/Props/Controllers/SearchOutlineController.cs index ba1464b..e769635 100644 --- a/Props/Controllers/SearchOutlineController.cs +++ b/Props/Controllers/SearchOutlineController.cs @@ -1,37 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Props.Data; using Props.Models.Search; -using Props.Services.Modules; +using Props.Models.User; +using Props.Shop.Framework; namespace Props.Controllers { public class SearchOutlineController : ApiControllerBase { - private SearchOutline defaultOutline = new SearchOutline(); - - - public SearchOutlineController() + private ApplicationDbContext dbContext; + private UserManager userManager; + public SearchOutlineController(UserManager userManager, ApplicationDbContext dbContext) { + this.userManager = userManager; + this.dbContext = dbContext; + } + + [HttpDelete] + [Authorize] + [Route("{name:required}")] + public async Task DeleteSearchOutline(string name) + { + if (string.IsNullOrEmpty(name)) + { + return BadRequest(); + } + ApplicationUser user = await userManager.GetUserAsync(User); + SearchOutlinePreferences searchOutlinePrefs = user.searchOutlinePreferences; + searchOutlinePrefs.SearchOutlines.Remove(searchOutlinePrefs.SearchOutlines.Single((outline) => name.Equals(outline.Name))); + await userManager.UpdateAsync(user); + return NoContent(); + } + + [HttpPost] + [Authorize] + [Route("{name:required}")] + public async Task PostSearchOutline(string name) + { + if (string.IsNullOrEmpty(name)) return BadRequest(); + ApplicationUser user = await userManager.GetUserAsync(User); + SearchOutline searchOutline = user.searchOutlinePreferences.SearchOutlines.SingleOrDefault((outline) => name.Equals(outline.Name)); + if (searchOutline != null) return BadRequest(); + searchOutline = new SearchOutline(); + searchOutline.Name = name; + user.searchOutlinePreferences.SearchOutlines.Add(searchOutline); + await userManager.UpdateAsync(user); + return NoContent(); + } + + [HttpPut] + [Authorize] + [Route("{name:required}/Filters")] + public async Task PutFilters(string name, Filters filters) + { + if (string.IsNullOrEmpty(name)) return BadRequest(); + ApplicationUser user = await userManager.GetUserAsync(User); + SearchOutline searchOutline = await GetSearchOutlineByName(name); + if (searchOutline == null) return BadRequest(); + searchOutline.Filters = filters; + await userManager.UpdateAsync(user); + return NoContent(); + } + + [HttpPut] + [Authorize] + [Route("{outlineName:required}/DisabledShops")] + public async Task PutShopSelection(string outlineName, ISet disabledShops) + { + if (string.IsNullOrEmpty(outlineName)) return BadRequest(); + if (disabledShops == null) return BadRequest(); + ApplicationUser user = await userManager.GetUserAsync(User); + SearchOutline searchOutline = await GetSearchOutlineByName(outlineName); + if (searchOutline == null) return BadRequest(); + + searchOutline.DisabledShops.Clear(); + searchOutline.DisabledShops.UnionWith(disabledShops); + await userManager.UpdateAsync(user); + return NoContent(); + } + + [HttpPut] + [Authorize] + [Route("{oldName:required}/Name/{newName:required}")] + public async Task PutName(string oldName, string newName) + { + if (oldName == newName) return BadRequest(); + ApplicationUser user = await userManager.GetUserAsync(User); + SearchOutline outline = await GetSearchOutlineByName(oldName); + if (outline == null) return BadRequest(); + if (user.searchOutlinePreferences.SearchOutlines.Any((outline) => outline.Name.Equals(newName))) return BadRequest(); + outline.Name = newName; + if (user.searchOutlinePreferences.NameOfLastUsed == oldName) + { + user.searchOutlinePreferences.NameOfLastUsed = newName; + } + await userManager.UpdateAsync(user); + return NoContent(); + } + + [HttpPut] + [Authorize] + [Route("{name:required}/LastUsed")] + public async Task PutLastUsed(string name) + { + SearchOutline outline = await GetSearchOutlineByName(name); + if (outline == null) return BadRequest(); + ApplicationUser user = await userManager.GetUserAsync(User); + user.searchOutlinePreferences.NameOfLastUsed = name; + await userManager.UpdateAsync(user); + return NoContent(); } [HttpGet] - [Route("Filters")] - public IActionResult GetFilters() + [Authorize] + [Route("{name:required}/Filters")] + public async Task GetFilters(string name) { - return Ok(defaultOutline.Filters); + Filters filters = (await GetSearchOutlineByName(name))?.Filters; + if (filters == null) return BadRequest(); + return Ok(filters); } [HttpGet] - [Route("DisabledShops")] - public IActionResult GetDisabledShops() + [Authorize] + [Route("{name:required}/DisabledShops")] + public async Task GetDisabledShops(string name) { - return Ok(defaultOutline.Enabled); + SearchOutline searchOutline = await GetSearchOutlineByName(name); + if (searchOutline == null) + { + return BadRequest(); + } + return Ok(searchOutline.DisabledShops); } [HttpGet] - [Route("SearchOutlineName")] - public IActionResult GetSearchOutlineName() + [Authorize] + [Route("Names")] + public async Task GetSearchOutlineNames() { - return Ok(defaultOutline.Name); + ApplicationUser user = await userManager.GetUserAsync(User); + return Ok(user.searchOutlinePreferences.SearchOutlines.Select((outline, Index) => outline.Name)); + } + + [HttpGet] + [Authorize] + [Route("LastUsed")] + public async Task GetLastSearchOutlineName() + { + SearchOutline searchOutline = await GetLastUsedSearchOutline(); + + return Ok(searchOutline?.Name); + } + + [HttpGet] + [Route("DefaultDisabledShops")] + public IActionResult GetDefaultDisabledShops() + { + return Ok(new SearchOutline.ShopSelector()); + } + + [HttpGet] + [Route("DefaultFilters")] + public IActionResult GetDefaultFilter() + { + return Ok(new Filters()); + } + + [HttpGet] + [Route("DefaultName")] + public async Task GetDefaultName() + { + string nameTemplate = "Search Outline {0}"; + if (User.Identity.IsAuthenticated) + { + ApplicationUser user = await userManager.GetUserAsync(User); + int number = user.searchOutlinePreferences.SearchOutlines.Count; + string name = null; + do + { + name = string.Format(nameTemplate, number); + number += 1; + } while (user.searchOutlinePreferences.SearchOutlines.Any((outline) => name.Equals(outline.Name))); + return Ok(name); + } + return Ok("Search Outline"); + } + + private async Task GetLastUsedSearchOutline() + { + if (!User.Identity.IsAuthenticated) return null; + ApplicationUser user = await userManager.GetUserAsync(User); + return user.searchOutlinePreferences.SearchOutlines.SingleOrDefault((outline) => outline.Name.Equals(user.searchOutlinePreferences.NameOfLastUsed)); + } + + private async Task GetSearchOutlineByName(string name) + { + if (name == null) throw new ArgumentNullException("name"); + if (!User.Identity.IsAuthenticated) return null; + ApplicationUser user = await userManager.GetUserAsync(User); + return user.searchOutlinePreferences.SearchOutlines.SingleOrDefault(outline => outline.Name.Equals(name)); } } } \ No newline at end of file diff --git a/Props/Controllers/UserController.cs b/Props/Controllers/UserController.cs new file mode 100644 index 0000000..368e437 --- /dev/null +++ b/Props/Controllers/UserController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Props.Controllers +{ + public class UserController : ApiControllerBase + { + [HttpGet] + [Route("LoggedIn")] + public IActionResult GetLoggedIn() + { + return Ok(User.Identity.IsAuthenticated); + } + } +} \ No newline at end of file diff --git a/Props/Data/ApplicationDbContext.cs b/Props/Data/ApplicationDbContext.cs index d9febdc..93ef5b1 100644 --- a/Props/Data/ApplicationDbContext.cs +++ b/Props/Data/ApplicationDbContext.cs @@ -17,7 +17,7 @@ namespace Props.Data { public class ApplicationDbContext : IdentityDbContext { - public DbSet Keywords { get; set; } + public DbSet QueryWords { get; set; } public DbSet ProductListingInfos { get; set; } @@ -43,14 +43,14 @@ namespace Props.Data ); modelBuilder.Entity() - .Property(e => e.Enabled) + .Property(e => e.DisabledShops) .HasConversion( v => JsonSerializer.Serialize(v, null), - v => JsonSerializer.Deserialize(v, null), - new ValueComparer( + v => JsonSerializer.Deserialize(v, null), + new ValueComparer( (a, b) => a.Equals(b), c => c.GetHashCode(), - c => c.Copy() + c => new SearchOutline.ShopSelector(c) ) ); diff --git a/Props/Data/Migrations/20210809194646_InitialCreate.Designer.cs b/Props/Data/Migrations/20210817042955_InitialCreate.Designer.cs similarity index 94% rename from Props/Data/Migrations/20210809194646_InitialCreate.Designer.cs rename to Props/Data/Migrations/20210817042955_InitialCreate.Designer.cs index e0c0b92..515a81a 100644 --- a/Props/Data/Migrations/20210809194646_InitialCreate.Designer.cs +++ b/Props/Data/Migrations/20210817042955_InitialCreate.Designer.cs @@ -9,7 +9,7 @@ using Props.Data; namespace Props.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210809194646_InitialCreate")] + [Migration("20210817042955_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -165,7 +165,6 @@ namespace Props.Data.Migrations .HasColumnType("TEXT"); b.Property("ProfileName") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); @@ -214,7 +213,7 @@ namespace Props.Data.Migrations b.HasKey("Id"); - b.ToTable("Keywords"); + b.ToTable("QueryWords"); }); modelBuilder.Entity("Props.Models.Search.SearchOutline", b => @@ -223,11 +222,7 @@ namespace Props.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Enabled") + b.Property("DisabledShops") .IsRequired() .HasColumnType("TEXT"); @@ -238,13 +233,11 @@ namespace Props.Data.Migrations .IsRequired() .HasColumnType("TEXT"); - b.Property("SearchOutlinePreferencesId") + b.Property("SearchOutlinePreferencesId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("ApplicationUserId"); - b.HasIndex("SearchOutlinePreferencesId"); b.ToTable("SearchOutline"); @@ -320,16 +313,14 @@ namespace Props.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ActiveSearchOutlineId") - .HasColumnType("INTEGER"); - b.Property("ApplicationUserId") .IsRequired() .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("NameOfLastUsed") + .HasColumnType("TEXT"); - b.HasIndex("ActiveSearchOutlineId"); + b.HasKey("Id"); b.HasIndex("ApplicationUserId") .IsUnique(); @@ -440,33 +431,23 @@ namespace Props.Data.Migrations modelBuilder.Entity("Props.Models.Search.SearchOutline", b => { - b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") - .WithMany() - .HasForeignKey("ApplicationUserId") + b.HasOne("Props.Models.User.SearchOutlinePreferences", "SearchOutlinePreferences") + .WithMany("SearchOutlines") + .HasForeignKey("SearchOutlinePreferencesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Props.Models.User.SearchOutlinePreferences", null) - .WithMany("SearchOutlines") - .HasForeignKey("SearchOutlinePreferencesId"); - - b.Navigation("ApplicationUser"); + b.Navigation("SearchOutlinePreferences"); }); modelBuilder.Entity("Props.Models.User.SearchOutlinePreferences", b => { - b.HasOne("Props.Models.Search.SearchOutline", "ActiveSearchOutline") - .WithMany() - .HasForeignKey("ActiveSearchOutlineId"); - b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") .WithOne("searchOutlinePreferences") .HasForeignKey("Props.Models.User.SearchOutlinePreferences", "ApplicationUserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("ActiveSearchOutline"); - b.Navigation("ApplicationUser"); }); diff --git a/Props/Data/Migrations/20210809194646_InitialCreate.cs b/Props/Data/Migrations/20210817042955_InitialCreate.cs similarity index 87% rename from Props/Data/Migrations/20210809194646_InitialCreate.cs rename to Props/Data/Migrations/20210817042955_InitialCreate.cs index a9b4715..0a3a91d 100644 --- a/Props/Data/Migrations/20210809194646_InitialCreate.cs +++ b/Props/Data/Migrations/20210817042955_InitialCreate.cs @@ -46,20 +46,6 @@ namespace Props.Data.Migrations table.PrimaryKey("PK_AspNetUsers", x => x.Id); }); - migrationBuilder.CreateTable( - name: "Keywords", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Word = table.Column(type: "TEXT", nullable: false), - Hits = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Keywords", x => x.Id); - }); - migrationBuilder.CreateTable( name: "ProductListingInfos", columns: table => new @@ -76,6 +62,20 @@ namespace Props.Data.Migrations table.PrimaryKey("PK_ProductListingInfos", x => x.Id); }); + migrationBuilder.CreateTable( + name: "QueryWords", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Word = table.Column(type: "TEXT", nullable: false), + Hits = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_QueryWords", x => x.Id); + }); + migrationBuilder.CreateTable( name: "AspNetRoleClaims", columns: table => new @@ -211,7 +211,7 @@ namespace Props.Data.Migrations .Annotation("Sqlite:Autoincrement", true), ApplicationUserId = table.Column(type: "TEXT", nullable: false), Order = table.Column(type: "TEXT", nullable: false), - ProfileName = table.Column(type: "TEXT", nullable: false) + ProfileName = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -224,6 +224,26 @@ namespace Props.Data.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "SearchOutlinePreferences", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: false), + NameOfLastUsed = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SearchOutlinePreferences", x => x.Id); + table.ForeignKey( + name: "FK_SearchOutlinePreferences_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "QueryWordInfoQueryWordInfo", columns: table => new @@ -235,15 +255,15 @@ namespace Props.Data.Migrations { table.PrimaryKey("PK_QueryWordInfoQueryWordInfo", x => new { x.FollowingId, x.PrecedingId }); table.ForeignKey( - name: "FK_QueryWordInfoQueryWordInfo_Keywords_FollowingId", + name: "FK_QueryWordInfoQueryWordInfo_QueryWords_FollowingId", column: x => x.FollowingId, - principalTable: "Keywords", + principalTable: "QueryWords", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_QueryWordInfoQueryWordInfo_Keywords_PrecedingId", + name: "FK_QueryWordInfoQueryWordInfo_QueryWords_PrecedingId", column: x => x.PrecedingId, - principalTable: "Keywords", + principalTable: "QueryWords", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); @@ -254,49 +274,22 @@ namespace Props.Data.Migrations { Id = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - ApplicationUserId = table.Column(type: "TEXT", nullable: false), + SearchOutlinePreferencesId = table.Column(type: "INTEGER", nullable: false), Name = table.Column(type: "TEXT", nullable: false), Filters = table.Column(type: "TEXT", nullable: true), - Enabled = table.Column(type: "TEXT", nullable: false), - SearchOutlinePreferencesId = table.Column(type: "INTEGER", nullable: true) + DisabledShops = 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", + name: "FK_SearchOutline_SearchOutlinePreferences_SearchOutlinePreferencesId", + column: x => x.SearchOutlinePreferencesId, + principalTable: "SearchOutlinePreferences", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "SearchOutlinePreferences", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApplicationUserId = table.Column(type: "TEXT", nullable: false), - ActiveSearchOutlineId = table.Column(type: "INTEGER", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_SearchOutlinePreferences", x => x.Id); - table.ForeignKey( - name: "FK_SearchOutlinePreferences_AspNetUsers_ApplicationUserId", - column: x => x.ApplicationUserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_SearchOutlinePreferences_SearchOutline_ActiveSearchOutlineId", - column: x => x.ActiveSearchOutlineId, - principalTable: "SearchOutline", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - migrationBuilder.CreateIndex( name: "IX_ApplicationPreferences_ApplicationUserId", table: "ApplicationPreferences", @@ -351,50 +344,20 @@ namespace Props.Data.Migrations column: "ApplicationUserId", unique: true); - migrationBuilder.CreateIndex( - name: "IX_SearchOutline_ApplicationUserId", - table: "SearchOutline", - column: "ApplicationUserId"); - migrationBuilder.CreateIndex( name: "IX_SearchOutline_SearchOutlinePreferencesId", table: "SearchOutline", column: "SearchOutlinePreferencesId"); - migrationBuilder.CreateIndex( - name: "IX_SearchOutlinePreferences_ActiveSearchOutlineId", - table: "SearchOutlinePreferences", - column: "ActiveSearchOutlineId"); - migrationBuilder.CreateIndex( name: "IX_SearchOutlinePreferences_ApplicationUserId", table: "SearchOutlinePreferences", column: "ApplicationUserId", unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_SearchOutline_SearchOutlinePreferences_SearchOutlinePreferencesId", - table: "SearchOutline", - column: "SearchOutlinePreferencesId", - principalTable: "SearchOutlinePreferences", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_SearchOutline_AspNetUsers_ApplicationUserId", - table: "SearchOutline"); - - migrationBuilder.DropForeignKey( - name: "FK_SearchOutlinePreferences_AspNetUsers_ApplicationUserId", - table: "SearchOutlinePreferences"); - - migrationBuilder.DropForeignKey( - name: "FK_SearchOutline_SearchOutlinePreferences_SearchOutlinePreferencesId", - table: "SearchOutline"); - migrationBuilder.DropTable( name: "ApplicationPreferences"); @@ -422,20 +385,20 @@ namespace Props.Data.Migrations migrationBuilder.DropTable( name: "ResultsPreferences"); + migrationBuilder.DropTable( + name: "SearchOutline"); + migrationBuilder.DropTable( name: "AspNetRoles"); migrationBuilder.DropTable( - name: "Keywords"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); + name: "QueryWords"); migrationBuilder.DropTable( name: "SearchOutlinePreferences"); migrationBuilder.DropTable( - name: "SearchOutline"); + name: "AspNetUsers"); } } } diff --git a/Props/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Props/Data/Migrations/ApplicationDbContextModelSnapshot.cs index af01399..3d03070 100644 --- a/Props/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Props/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -163,7 +163,6 @@ namespace Props.Data.Migrations .HasColumnType("TEXT"); b.Property("ProfileName") - .IsRequired() .HasColumnType("TEXT"); b.HasKey("Id"); @@ -212,7 +211,7 @@ namespace Props.Data.Migrations b.HasKey("Id"); - b.ToTable("Keywords"); + b.ToTable("QueryWords"); }); modelBuilder.Entity("Props.Models.Search.SearchOutline", b => @@ -221,11 +220,7 @@ namespace Props.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Enabled") + b.Property("DisabledShops") .IsRequired() .HasColumnType("TEXT"); @@ -236,13 +231,11 @@ namespace Props.Data.Migrations .IsRequired() .HasColumnType("TEXT"); - b.Property("SearchOutlinePreferencesId") + b.Property("SearchOutlinePreferencesId") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("ApplicationUserId"); - b.HasIndex("SearchOutlinePreferencesId"); b.ToTable("SearchOutline"); @@ -318,16 +311,14 @@ namespace Props.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("ActiveSearchOutlineId") - .HasColumnType("INTEGER"); - b.Property("ApplicationUserId") .IsRequired() .HasColumnType("TEXT"); - b.HasKey("Id"); + b.Property("NameOfLastUsed") + .HasColumnType("TEXT"); - b.HasIndex("ActiveSearchOutlineId"); + b.HasKey("Id"); b.HasIndex("ApplicationUserId") .IsUnique(); @@ -438,33 +429,23 @@ namespace Props.Data.Migrations modelBuilder.Entity("Props.Models.Search.SearchOutline", b => { - b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") - .WithMany() - .HasForeignKey("ApplicationUserId") + b.HasOne("Props.Models.User.SearchOutlinePreferences", "SearchOutlinePreferences") + .WithMany("SearchOutlines") + .HasForeignKey("SearchOutlinePreferencesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Props.Models.User.SearchOutlinePreferences", null) - .WithMany("SearchOutlines") - .HasForeignKey("SearchOutlinePreferencesId"); - - b.Navigation("ApplicationUser"); + b.Navigation("SearchOutlinePreferences"); }); modelBuilder.Entity("Props.Models.User.SearchOutlinePreferences", b => { - b.HasOne("Props.Models.Search.SearchOutline", "ActiveSearchOutline") - .WithMany() - .HasForeignKey("ActiveSearchOutlineId"); - b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") .WithOne("searchOutlinePreferences") .HasForeignKey("Props.Models.User.SearchOutlinePreferences", "ApplicationUserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("ActiveSearchOutline"); - b.Navigation("ApplicationUser"); }); diff --git a/Props/Models/Search/SearchOutline.cs b/Props/Models/Search/SearchOutline.cs index 66d0bec..c8db5c5 100644 --- a/Props/Models/Search/SearchOutline.cs +++ b/Props/Models/Search/SearchOutline.cs @@ -11,53 +11,45 @@ namespace Props.Models.Search { public int Id { get; set; } - [Required] - public string ApplicationUserId { get; set; } + public int SearchOutlinePreferencesId { get; set; } [Required] - public virtual ApplicationUser ApplicationUser { get; set; } + public virtual SearchOutlinePreferences SearchOutlinePreferences { get; set; } [Required] - public string Name { get; set; } = "Default"; - - public Filters Filters { get; set; } = new Filters(); + public string Name { get; set; } + public Filters Filters { get; set; } [Required] - public ShopsDisabled Enabled { get; set; } = new ShopsDisabled(); + public ShopSelector DisabledShops { get; set; } - public sealed class ShopsDisabled : HashSet + public sealed class ShopSelector : HashSet { - public int TotalShops { get; set; } public bool this[string name] { get { - return !this.Contains(name); + return this.Contains(name); } set { - if (value == false && TotalShops - Count <= 1) return; if (value) { - this.Remove(name); + this.Add(name); } else { - this.Add(name); + this.Remove(name); } } } - public ShopsDisabled Copy() + public ShopSelector() { - ShopsDisabled copy = new ShopsDisabled(); - copy.Union(this); - return copy; } - public bool IsShopToggleable(string shop) + public ShopSelector(IEnumerable disabledShops) : base(disabledShops) { - return (!Contains(shop) && TotalShops - Count > 1) || Contains(shop); } } @@ -70,27 +62,27 @@ namespace Props.Models.Search SearchOutline other = (SearchOutline)obj; return Id == other.Id && + Name.Equals(other.Name) && Filters.Equals(other.Filters) && - Enabled.Equals(other.Enabled); + DisabledShops.Equals(other.DisabledShops); } public override int GetHashCode() { - return HashCode.Combine(Id, Name); + return HashCode.Combine(Id, Name, Filters, DisabledShops); } public SearchOutline() { - this.Name = "Default"; this.Filters = new Filters(); - this.Enabled = new ShopsDisabled(); + this.DisabledShops = new ShopSelector(); } - public SearchOutline(string name, Filters filters, ShopsDisabled disabled) + public SearchOutline(string name, Filters filters, ShopSelector disabled) { this.Name = name; this.Filters = filters; - this.Enabled = disabled; + this.DisabledShops = disabled; } } } \ No newline at end of file diff --git a/Props/Models/User/ResultsPreferences.cs b/Props/Models/User/ResultsPreferences.cs index 3289f41..b09e004 100644 --- a/Props/Models/User/ResultsPreferences.cs +++ b/Props/Models/User/ResultsPreferences.cs @@ -21,7 +21,6 @@ namespace Props.Models [Required] public IList Order { get; set; } - [Required] public string ProfileName { get; set; } public ResultsPreferences() diff --git a/Props/Models/User/SearchOutlinePreferences.cs b/Props/Models/User/SearchOutlinePreferences.cs index 5b6be55..bc23516 100644 --- a/Props/Models/User/SearchOutlinePreferences.cs +++ b/Props/Models/User/SearchOutlinePreferences.cs @@ -16,22 +16,19 @@ namespace Props.Models.User public virtual ApplicationUser ApplicationUser { get; set; } [Required] - public virtual ISet SearchOutlines { get; set; } + public virtual IList SearchOutlines { get; set; } - [Required] - public virtual SearchOutline ActiveSearchOutline { get; set; } + public string NameOfLastUsed { get; set; } public SearchOutlinePreferences() { - SearchOutlines = new HashSet(); - ActiveSearchOutline = new SearchOutline(); - SearchOutlines.Add(ActiveSearchOutline); + SearchOutlines = new List(); } - public SearchOutlinePreferences(ISet searchOutlines, SearchOutline activeSearchOutline) + public SearchOutlinePreferences(List searchOutlines, string nameOfLastUsed) { this.SearchOutlines = searchOutlines; - this.ActiveSearchOutline = activeSearchOutline; + this.NameOfLastUsed = nameOfLastUsed; } } } \ No newline at end of file diff --git a/Props/Options/MetricsOptions.cs b/Props/Options/MetricsOptions.cs new file mode 100644 index 0000000..4babdf3 --- /dev/null +++ b/Props/Options/MetricsOptions.cs @@ -0,0 +1,9 @@ +namespace Props.Options +{ + public class MetricsOptions + { + public const string Metrics = "Metrics"; + public int MaxQueryWords { get; set; } + public int MaxProductListings { get; set; } + } +} \ No newline at end of file diff --git a/Props/Pages/Search.cshtml b/Props/Pages/Search.cshtml index 5c4d682..8b682a3 100644 --- a/Props/Pages/Search.cshtml +++ b/Props/Pages/Search.cshtml @@ -8,255 +8,298 @@ ViewData["Specific"] = "Search"; } -
-
+
+
+ aria-label="Search" aria-describedby="search-btn" id="search-bar" value="@Model.SearchQuery" + x-model="query"> - +
-
-
-

Configuration

- -
-
-
-

Price

-
- -
-
- -
- $ - - .00 -
-
-
- -
- $ - - .00 -
-
-
- -
-
- -
- $ - - .00 -
-
-
-
- - -
-
-
-
-

Metrics

-
- -
- - Purchases -
-
-
-
- - -
-
-
- -
- - Reviews -
-
-
-
- - -
-
-
- - -
-
-
-
- - -
-
-
-
-

Shops Enabled

-
- @foreach (string shopName in Model.SearchManager.ShopManager.GetAllShopNames()) - { -
- - -
- } -
-
-
+
+

+ + Configuration +

+
-
- - -