Implemented groundwork for search configuration.
This commit is contained in:
		| @@ -15,7 +15,7 @@ namespace Props.Shop.Adafruit.Api | |||||||
|         private Dictionary<string, List<ProductListing>> listings = new Dictionary<string, List<ProductListing>>(); |         private Dictionary<string, List<ProductListing>> listings = new Dictionary<string, List<ProductListing>>(); | ||||||
|         private bool requested = false; |         private bool requested = false; | ||||||
|         public DateTime TimeOfLastRequest { get; private set; } |         public DateTime TimeOfLastRequest { get; private set; } | ||||||
|         public bool RequestReady => DateTime.Now - TimeOfLastRequest > TimeSpan.FromMinutes(minutesPerRequest); |         public bool RequestReady => !requested || DateTime.Now - TimeOfLastRequest > TimeSpan.FromMinutes(minutesPerRequest); | ||||||
|  |  | ||||||
|         public ProductListingManager(int requestsPerMinute = 5) |         public ProductListingManager(int requestsPerMinute = 5) | ||||||
|         { |         { | ||||||
| @@ -27,10 +27,10 @@ namespace Props.Shop.Adafruit.Api | |||||||
|             requested = true; |             requested = true; | ||||||
|             TimeOfLastRequest = DateTime.Now; |             TimeOfLastRequest = DateTime.Now; | ||||||
|             HttpResponseMessage response = await http.GetAsync("/products"); |             HttpResponseMessage response = await http.GetAsync("/products"); | ||||||
|             SetListings(await response.Content.ReadAsStringAsync()); |             SetListingsData(await response.Content.ReadAsStringAsync()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void SetListings(string data) |         public void SetListingsData(string data) | ||||||
|         { |         { | ||||||
|             ListingsParser listingsParser = new ListingsParser(data); |             ListingsParser listingsParser = new ListingsParser(data); | ||||||
|             foreach (ProductListing listing in listingsParser.ProductListings) |             foreach (ProductListing listing in listingsParser.ProductListings) | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ namespace Props.Shop.Framework | |||||||
|         public int MinPurchases { get; set; } |         public int MinPurchases { get; set; } | ||||||
|         public bool KeepUnknownPurchaseCount { get; set; } = true; |         public bool KeepUnknownPurchaseCount { get; set; } = true; | ||||||
|         public int MinReviews { get; set; } |         public int MinReviews { get; set; } | ||||||
|         public bool KeepUnknownRatingCount { get; set; } = true; |         public bool KeepUnknownReviewCount { get; set; } = true; | ||||||
|         public bool EnableMaxShippingFee { get; set; } |         public bool EnableMaxShippingFee { get; set; } | ||||||
|         private int maxShippingFee; |         private int maxShippingFee; | ||||||
|  |  | ||||||
| @@ -59,7 +59,7 @@ namespace Props.Shop.Framework | |||||||
|                 MinPurchases == other.MinPurchases && |                 MinPurchases == other.MinPurchases && | ||||||
|                 KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount && |                 KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount && | ||||||
|                 MinReviews == other.MinReviews && |                 MinReviews == other.MinReviews && | ||||||
|                 KeepUnknownRatingCount == other.KeepUnknownRatingCount && |                 KeepUnknownReviewCount == other.KeepUnknownReviewCount && | ||||||
|                 EnableMaxShippingFee == other.EnableMaxShippingFee && |                 EnableMaxShippingFee == other.EnableMaxShippingFee && | ||||||
|                 MaxShippingFee == other.MaxShippingFee && |                 MaxShippingFee == other.MaxShippingFee && | ||||||
|                 KeepUnknownShipping == other.KeepUnknownShipping; |                 KeepUnknownShipping == other.KeepUnknownShipping; | ||||||
| @@ -81,5 +81,16 @@ namespace Props.Shop.Framework | |||||||
|         { |         { | ||||||
|             return (Filters)this.MemberwiseClone(); |             return (Filters)this.MemberwiseClone(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public bool Validate(ProductListing listing) | ||||||
|  |         { | ||||||
|  |             if (listing.Shipping == null && !KeepUnknownShipping || (EnableMaxShippingFee && 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; | ||||||
|  |             if ((listing.PurchaseCount == null && !KeepUnknownPurchaseCount) || MinPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) return false; | ||||||
|  |             if ((listing.ReviewCount == null && !KeepUnknownReviewCount) || MinReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) return false; | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -13,7 +13,7 @@ namespace Props.Shop.Adafruit.Tests.Api | |||||||
|         public async Task TestSearch() |         public async Task TestSearch() | ||||||
|         { |         { | ||||||
|             ProductListingManager mockProductListingManager = new ProductListingManager(); |             ProductListingManager mockProductListingManager = new ProductListingManager(); | ||||||
|             mockProductListingManager.SetListings(File.ReadAllText("./Assets/products.json")); |             mockProductListingManager.SetListingsData(File.ReadAllText("./Assets/products.json")); | ||||||
|             List<ProductListing> results = new List<ProductListing>(); |             List<ProductListing> results = new List<ProductListing>(); | ||||||
|             await foreach (ProductListing item in mockProductListingManager.Search("arduino", 0.5f)) |             await foreach (ProductListing item in mockProductListingManager.Search("arduino", 0.5f)) | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ module.exports = { | |||||||
|         "node": true, |         "node": true, | ||||||
|     }, |     }, | ||||||
|     "extends": "eslint:recommended", |     "extends": "eslint:recommended", | ||||||
|     "parser": "babel-eslint", |     "parser": "@babel/eslint-parser", | ||||||
|     "rules": { |     "rules": { | ||||||
|         "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", |         "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", | ||||||
|         "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", |         "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", | ||||||
| @@ -22,6 +22,10 @@ module.exports = { | |||||||
|             "always" |             "always" | ||||||
|         ], |         ], | ||||||
|         "comma-dangle": ["error", "only-multiline"], |         "comma-dangle": ["error", "only-multiline"], | ||||||
|         "space-before-function-paren": ["error", "never"] |         "space-before-function-paren": ["error", { | ||||||
|  |             "anonymous": "always", | ||||||
|  |             "named": "never", | ||||||
|  |             "asyncArrow": "always" | ||||||
|  |         }] | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -6,8 +6,9 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| <div class="flex-grow-1 d-flex flex-column justify-content-center"> | <div class="flex-grow-1 d-flex flex-column justify-content-center"> | ||||||
|     <div class="jumbotron border-top border-bottom"> |     <div class="jumbotron sole d-flex flex-column align-content-center"> | ||||||
|         <h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1> |         <img alt="Props logo" src="~/images/logo-simplified.svg" class="img-fluid" style="max-height: 180px;" asp-append-version="true" /> | ||||||
|  |         <h1 class="mt-3 mb-4 text-center">@ViewData["Title"]</h1> | ||||||
|         <div class="my-3 row justify-content-md-center"> |         <div class="my-3 row justify-content-md-center"> | ||||||
|             <div class="col-md-4"> |             <div class="col-md-4"> | ||||||
|                 <form id="account" method="post"> |                 <form id="account" method="post"> | ||||||
|   | |||||||
| @@ -13,3 +13,5 @@ | |||||||
|     <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li> |     <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li> | ||||||
|     <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li> |     <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li> | ||||||
| </ul> | </ul> | ||||||
|  |  | ||||||
|  | @* TODO: Finish styling this page. *@ | ||||||
| @@ -5,8 +5,9 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| <div class="flex-grow-1 d-flex flex-column justify-content-center"> | <div class="flex-grow-1 d-flex flex-column justify-content-center"> | ||||||
|     <div class="jumbotron border-top border-bottom"> |     <div class="jumbotron sole d-flex flex-column align-content-center"> | ||||||
|         <h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1> |         <img alt="Props logo" src="~/images/logo-simplified.svg" class="img-fluid" style="max-height: 180px;" asp-append-version="true" /> | ||||||
|  |         <h1 class="mt-3 mb-4 text-center">@ViewData["Title"]</h1> | ||||||
|         <div class="my-3 row justify-content-md-center"> |         <div class="my-3 row justify-content-md-center"> | ||||||
|             <div class="col-md-4"> |             <div class="col-md-4"> | ||||||
|                 <form asp-route-returnUrl="@Model.ReturnUrl" method="post"> |                 <form asp-route-returnUrl="@Model.ReturnUrl" method="post"> | ||||||
|   | |||||||
| @@ -20,4 +20,4 @@ | |||||||
|         </p> |         </p> | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @* TODO: https://aka.ms/aspaccountconf *@ | @* TODO: Do something about this. *@ | ||||||
							
								
								
									
										41
									
								
								Props/Controllers/SearchController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Props/Controllers/SearchController.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | using Microsoft.AspNetCore.Mvc; | ||||||
|  | using Props.Models.Search; | ||||||
|  | using Props.Services.Modules; | ||||||
|  |  | ||||||
|  | namespace Props.Controllers | ||||||
|  | { | ||||||
|  |     [ApiController] | ||||||
|  |     [Route("api/[Controller]")] | ||||||
|  |     public class SearchController : ControllerBase | ||||||
|  |     { | ||||||
|  |         private SearchOutline defaultOutline = new SearchOutline(); | ||||||
|  |         IShopManager shopManager; | ||||||
|  |  | ||||||
|  |         public SearchController(IShopManager shopManager) | ||||||
|  |         { | ||||||
|  |             this.shopManager = shopManager; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [HttpGet] | ||||||
|  |         [Route("Shops/Available")] | ||||||
|  |         public IActionResult GetAvailableShops() | ||||||
|  |         { | ||||||
|  |             return Ok(shopManager.AvailableShops()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         [HttpGet] | ||||||
|  |         [Route("Default/Outline/Filters")] | ||||||
|  |         public IActionResult GetDefaultFilters() | ||||||
|  |         { | ||||||
|  |             return Ok(defaultOutline.Filters); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [HttpGet] | ||||||
|  |         [Route("Default/Outline/DisabledShops")] | ||||||
|  |         public IActionResult GetDefaultDisabledShops() | ||||||
|  |         { | ||||||
|  |             return Ok(defaultOutline.Disabled); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; | |||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using Microsoft.EntityFrameworkCore.ChangeTracking; | using Microsoft.EntityFrameworkCore.ChangeTracking; | ||||||
| using Props.Models; | using Props.Models; | ||||||
|  | using Props.Models.Search; | ||||||
| using Props.Models.User; | using Props.Models.User; | ||||||
| using Props.Shop.Framework; | using Props.Shop.Framework; | ||||||
|  |  | ||||||
| @@ -15,6 +16,8 @@ namespace Props.Data | |||||||
| { | { | ||||||
|     public class ApplicationDbContext : IdentityDbContext<ApplicationUser> |     public class ApplicationDbContext : IdentityDbContext<ApplicationUser> | ||||||
|     { |     { | ||||||
|  |         DbSet<SearchOutline> SearchOutlines { get; set; } | ||||||
|  |         DbSet<ProductListingInfo> TrackedListings { get; set; } | ||||||
|         public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) |         public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) | ||||||
|             : base(options) |             : base(options) | ||||||
|         { |         { | ||||||
| @@ -37,11 +40,11 @@ namespace Props.Data | |||||||
|                 ); |                 ); | ||||||
|  |  | ||||||
|             modelBuilder.Entity<SearchOutline>() |             modelBuilder.Entity<SearchOutline>() | ||||||
|                 .Property(e => e.ShopStates) |                 .Property(e => e.Disabled) | ||||||
|                 .HasConversion( |                 .HasConversion( | ||||||
|                     v => JsonSerializer.Serialize(v, null), |                     v => JsonSerializer.Serialize(v, null), | ||||||
|                     v => JsonSerializer.Deserialize<SearchOutline.ShopToggler>(v, null), |                     v => JsonSerializer.Deserialize<SearchOutline.ShopsDisabled>(v, null), | ||||||
|                     new ValueComparer<SearchOutline.ShopToggler>( |                     new ValueComparer<SearchOutline.ShopsDisabled>( | ||||||
|                         (a, b) => a.Equals(b), |                         (a, b) => a.Equals(b), | ||||||
|                         c => c.GetHashCode(), |                         c => c.GetHashCode(), | ||||||
|                         c => c.Copy() |                         c => c.Copy() | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ using Props.Data; | |||||||
| namespace Props.Data.Migrations | namespace Props.Data.Migrations | ||||||
| { | { | ||||||
|     [DbContext(typeof(ApplicationDbContext))] |     [DbContext(typeof(ApplicationDbContext))] | ||||||
|     [Migration("20210721064503_InitialCreate")] |     [Migration("20210722180024_InitialCreate")] | ||||||
|     partial class InitialCreate |     partial class InitialCreate | ||||||
|     { |     { | ||||||
|         protected override void BuildTargetModel(ModelBuilder modelBuilder) |         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||||
| @@ -150,7 +150,80 @@ namespace Props.Data.Migrations | |||||||
|                     b.ToTable("AspNetUserTokens"); |                     b.ToTable("AspNetUserTokens"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Models.ApplicationUser", b => |             modelBuilder.Entity("Props.Models.ResultsPreferences", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ApplicationUserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Order") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProfileName") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("ApplicationUserId") | ||||||
|  |                         .IsUnique(); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("ResultsPreferences"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Props.Models.Search.ProductListingInfo", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<uint>("Hits") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<DateTime>("LastUpdated") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProductName") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ProductUrl") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("TrackedListings"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Props.Models.Search.SearchOutline", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("ApplicationUserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Disabled") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.Property<string>("Filters") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  | 
 | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  | 
 | ||||||
|  |                     b.HasIndex("ApplicationUserId"); | ||||||
|  | 
 | ||||||
|  |                     b.ToTable("SearchOutlines"); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity("Props.Models.User.ApplicationUser", b => | ||||||
|                 { |                 { | ||||||
|                     b.Property<string>("Id") |                     b.Property<string>("Id") | ||||||
|                         .HasColumnType("TEXT"); |                         .HasColumnType("TEXT"); | ||||||
| @@ -214,56 +287,6 @@ namespace Props.Data.Migrations | |||||||
|                     b.ToTable("AspNetUsers"); |                     b.ToTable("AspNetUsers"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Models.ResultsPreferences", b => |  | ||||||
|                 { |  | ||||||
|                     b.Property<int>("Id") |  | ||||||
|                         .ValueGeneratedOnAdd() |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<string>("ApplicationUserId") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<string>("Order") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
| 
 |  | ||||||
|                     b.HasKey("Id"); |  | ||||||
| 
 |  | ||||||
|                     b.HasIndex("ApplicationUserId") |  | ||||||
|                         .IsUnique(); |  | ||||||
| 
 |  | ||||||
|                     b.ToTable("ResultsPreferences"); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             modelBuilder.Entity("Props.Models.SearchOutline", b => |  | ||||||
|                 { |  | ||||||
|                     b.Property<int>("Id") |  | ||||||
|                         .ValueGeneratedOnAdd() |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<string>("ApplicationUserId") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<string>("Filters") |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<int>("MaxResults") |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
| 
 |  | ||||||
|                     b.Property<string>("ShopStates") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
| 
 |  | ||||||
|                     b.HasKey("Id"); |  | ||||||
| 
 |  | ||||||
|                     b.HasIndex("ApplicationUserId") |  | ||||||
|                         .IsUnique(); |  | ||||||
| 
 |  | ||||||
|                     b.ToTable("SearchOutline"); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => |             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.Property<int>("Id") |                     b.Property<int>("Id") | ||||||
| @@ -299,7 +322,7 @@ namespace Props.Data.Migrations | |||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -308,7 +331,7 @@ namespace Props.Data.Migrations | |||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -323,7 +346,7 @@ namespace Props.Data.Migrations | |||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
| 
 | 
 | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -332,7 +355,7 @@ namespace Props.Data.Migrations | |||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -341,32 +364,38 @@ namespace Props.Data.Migrations | |||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Models.ResultsPreferences", b => |             modelBuilder.Entity("Props.Models.ResultsPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("ResultsPreferences") |                         .WithOne("ResultsPreferences") | ||||||
|                         .HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId") |                         .HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Models.SearchOutline", b => |             modelBuilder.Entity("Props.Models.Search.SearchOutline", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("SearchOutline") |                         .WithMany("SearchOutlines") | ||||||
|                         .HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId") |                         .HasForeignKey("ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => |             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("ApplicationPreferences") |                         .WithOne("ApplicationPreferences") | ||||||
|                         .HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId") |                         .HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  | 
 | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             modelBuilder.Entity("Props.Models.ApplicationUser", b => |             modelBuilder.Entity("Props.Models.User.ApplicationUser", b => | ||||||
|                 { |                 { | ||||||
|                     b.Navigation("ApplicationPreferences") |                     b.Navigation("ApplicationPreferences") | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
| @@ -374,8 +403,7 @@ namespace Props.Data.Migrations | |||||||
|                     b.Navigation("ResultsPreferences") |                     b.Navigation("ResultsPreferences") | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
| 
 | 
 | ||||||
|                     b.Navigation("SearchOutline") |                     b.Navigation("SearchOutlines"); | ||||||
|                         .IsRequired(); |  | ||||||
|                 }); |                 }); | ||||||
| #pragma warning restore 612, 618 | #pragma warning restore 612, 618 | ||||||
|         } |         } | ||||||
| @@ -46,6 +46,22 @@ namespace Props.Data.Migrations | |||||||
|                     table.PrimaryKey("PK_AspNetUsers", x => x.Id); |                     table.PrimaryKey("PK_AspNetUsers", x => x.Id); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "TrackedListings", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|  |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|  |                     Hits = table.Column<uint>(type: "INTEGER", nullable: false), | ||||||
|  |                     LastUpdated = table.Column<DateTime>(type: "TEXT", nullable: false), | ||||||
|  |                     ProductUrl = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|  |                     ProductName = table.Column<string>(type: "TEXT", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_TrackedListings", x => x.Id); | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|             migrationBuilder.CreateTable( |             migrationBuilder.CreateTable( | ||||||
|                 name: "AspNetRoleClaims", |                 name: "AspNetRoleClaims", | ||||||
|                 columns: table => new |                 columns: table => new | ||||||
| @@ -180,7 +196,8 @@ namespace Props.Data.Migrations | |||||||
|                     Id = table.Column<int>(type: "INTEGER", nullable: false) |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|                         .Annotation("Sqlite:Autoincrement", true), |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|                     ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false), |                     ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|                     Order = table.Column<string>(type: "TEXT", nullable: false) |                     Order = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|  |                     ProfileName = table.Column<string>(type: "TEXT", nullable: false) | ||||||
|                 }, |                 }, | ||||||
|                 constraints: table => |                 constraints: table => | ||||||
|                 { |                 { | ||||||
| @@ -194,21 +211,20 @@ namespace Props.Data.Migrations | |||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|             migrationBuilder.CreateTable( |             migrationBuilder.CreateTable( | ||||||
|                 name: "SearchOutline", |                 name: "SearchOutlines", | ||||||
|                 columns: table => new |                 columns: table => new | ||||||
|                 { |                 { | ||||||
|                     Id = table.Column<int>(type: "INTEGER", nullable: false) |                     Id = table.Column<int>(type: "INTEGER", nullable: false) | ||||||
|                         .Annotation("Sqlite:Autoincrement", true), |                         .Annotation("Sqlite:Autoincrement", true), | ||||||
|                     ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false), |                     ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false), | ||||||
|                     Filters = table.Column<string>(type: "TEXT", nullable: true), |                     Filters = table.Column<string>(type: "TEXT", nullable: true), | ||||||
|                     MaxResults = table.Column<int>(type: "INTEGER", nullable: false), |                     Disabled = table.Column<string>(type: "TEXT", nullable: false) | ||||||
|                     ShopStates = table.Column<string>(type: "TEXT", nullable: false) |  | ||||||
|                 }, |                 }, | ||||||
|                 constraints: table => |                 constraints: table => | ||||||
|                 { |                 { | ||||||
|                     table.PrimaryKey("PK_SearchOutline", x => x.Id); |                     table.PrimaryKey("PK_SearchOutlines", x => x.Id); | ||||||
|                     table.ForeignKey( |                     table.ForeignKey( | ||||||
|                         name: "FK_SearchOutline_AspNetUsers_ApplicationUserId", |                         name: "FK_SearchOutlines_AspNetUsers_ApplicationUserId", | ||||||
|                         column: x => x.ApplicationUserId, |                         column: x => x.ApplicationUserId, | ||||||
|                         principalTable: "AspNetUsers", |                         principalTable: "AspNetUsers", | ||||||
|                         principalColumn: "Id", |                         principalColumn: "Id", | ||||||
| @@ -265,10 +281,9 @@ namespace Props.Data.Migrations | |||||||
|                 unique: true); |                 unique: true); | ||||||
| 
 | 
 | ||||||
|             migrationBuilder.CreateIndex( |             migrationBuilder.CreateIndex( | ||||||
|                 name: "IX_SearchOutline_ApplicationUserId", |                 name: "IX_SearchOutlines_ApplicationUserId", | ||||||
|                 table: "SearchOutline", |                 table: "SearchOutlines", | ||||||
|                 column: "ApplicationUserId", |                 column: "ApplicationUserId"); | ||||||
|                 unique: true); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         protected override void Down(MigrationBuilder migrationBuilder) |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
| @@ -295,7 +310,10 @@ namespace Props.Data.Migrations | |||||||
|                 name: "ResultsPreferences"); |                 name: "ResultsPreferences"); | ||||||
| 
 | 
 | ||||||
|             migrationBuilder.DropTable( |             migrationBuilder.DropTable( | ||||||
|                 name: "SearchOutline"); |                 name: "SearchOutlines"); | ||||||
|  | 
 | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "TrackedListings"); | ||||||
| 
 | 
 | ||||||
|             migrationBuilder.DropTable( |             migrationBuilder.DropTable( | ||||||
|                 name: "AspNetRoles"); |                 name: "AspNetRoles"); | ||||||
| @@ -148,7 +148,80 @@ namespace Props.Data.Migrations | |||||||
|                     b.ToTable("AspNetUserTokens"); |                     b.ToTable("AspNetUserTokens"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.ApplicationUser", b => |             modelBuilder.Entity("Props.Models.ResultsPreferences", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ApplicationUserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Order") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ProfileName") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ApplicationUserId") | ||||||
|  |                         .IsUnique(); | ||||||
|  |  | ||||||
|  |                     b.ToTable("ResultsPreferences"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("Props.Models.Search.ProductListingInfo", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  |  | ||||||
|  |                     b.Property<uint>("Hits") | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("LastUpdated") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ProductName") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ProductUrl") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("TrackedListings"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("Props.Models.Search.SearchOutline", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("INTEGER"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ApplicationUserId") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Disabled") | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Filters") | ||||||
|  |                         .HasColumnType("TEXT"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Id"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ApplicationUserId"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("SearchOutlines"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("Props.Models.User.ApplicationUser", b => | ||||||
|                 { |                 { | ||||||
|                     b.Property<string>("Id") |                     b.Property<string>("Id") | ||||||
|                         .HasColumnType("TEXT"); |                         .HasColumnType("TEXT"); | ||||||
| @@ -212,56 +285,6 @@ namespace Props.Data.Migrations | |||||||
|                     b.ToTable("AspNetUsers"); |                     b.ToTable("AspNetUsers"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.ResultsPreferences", b => |  | ||||||
|                 { |  | ||||||
|                     b.Property<int>("Id") |  | ||||||
|                         .ValueGeneratedOnAdd() |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
|  |  | ||||||
|                     b.Property<string>("ApplicationUserId") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
|  |  | ||||||
|                     b.Property<string>("Order") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
|  |  | ||||||
|                     b.HasKey("Id"); |  | ||||||
|  |  | ||||||
|                     b.HasIndex("ApplicationUserId") |  | ||||||
|                         .IsUnique(); |  | ||||||
|  |  | ||||||
|                     b.ToTable("ResultsPreferences"); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.SearchOutline", b => |  | ||||||
|                 { |  | ||||||
|                     b.Property<int>("Id") |  | ||||||
|                         .ValueGeneratedOnAdd() |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
|  |  | ||||||
|                     b.Property<string>("ApplicationUserId") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
|  |  | ||||||
|                     b.Property<string>("Filters") |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
|  |  | ||||||
|                     b.Property<int>("MaxResults") |  | ||||||
|                         .HasColumnType("INTEGER"); |  | ||||||
|  |  | ||||||
|                     b.Property<string>("ShopStates") |  | ||||||
|                         .IsRequired() |  | ||||||
|                         .HasColumnType("TEXT"); |  | ||||||
|  |  | ||||||
|                     b.HasKey("Id"); |  | ||||||
|  |  | ||||||
|                     b.HasIndex("ApplicationUserId") |  | ||||||
|                         .IsUnique(); |  | ||||||
|  |  | ||||||
|                     b.ToTable("SearchOutline"); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => |             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.Property<int>("Id") |                     b.Property<int>("Id") | ||||||
| @@ -297,7 +320,7 @@ namespace Props.Data.Migrations | |||||||
|  |  | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -306,7 +329,7 @@ namespace Props.Data.Migrations | |||||||
|  |  | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -321,7 +344,7 @@ namespace Props.Data.Migrations | |||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  |  | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -330,7 +353,7 @@ namespace Props.Data.Migrations | |||||||
|  |  | ||||||
|             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => |             modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", null) | ||||||
|                         .WithMany() |                         .WithMany() | ||||||
|                         .HasForeignKey("UserId") |                         .HasForeignKey("UserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
| @@ -339,32 +362,38 @@ namespace Props.Data.Migrations | |||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.ResultsPreferences", b => |             modelBuilder.Entity("Props.Models.ResultsPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("ResultsPreferences") |                         .WithOne("ResultsPreferences") | ||||||
|                         .HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId") |                         .HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.SearchOutline", b => |             modelBuilder.Entity("Props.Models.Search.SearchOutline", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("SearchOutline") |                         .WithMany("SearchOutlines") | ||||||
|                         .HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId") |                         .HasForeignKey("ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => |             modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Props.Models.ApplicationUser", null) |                     b.HasOne("Props.Models.User.ApplicationUser", "ApplicationUser") | ||||||
|                         .WithOne("ApplicationPreferences") |                         .WithOne("ApplicationPreferences") | ||||||
|                         .HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId") |                         .HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId") | ||||||
|                         .OnDelete(DeleteBehavior.Cascade) |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ApplicationUser"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("Props.Models.ApplicationUser", b => |             modelBuilder.Entity("Props.Models.User.ApplicationUser", b => | ||||||
|                 { |                 { | ||||||
|                     b.Navigation("ApplicationPreferences") |                     b.Navigation("ApplicationPreferences") | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
| @@ -372,8 +401,7 @@ namespace Props.Data.Migrations | |||||||
|                     b.Navigation("ResultsPreferences") |                     b.Navigation("ResultsPreferences") | ||||||
|                         .IsRequired(); |                         .IsRequired(); | ||||||
|  |  | ||||||
|                     b.Navigation("SearchOutline") |                     b.Navigation("SearchOutlines"); | ||||||
|                         .IsRequired(); |  | ||||||
|                 }); |                 }); | ||||||
| #pragma warning restore 612, 618 | #pragma warning restore 612, 618 | ||||||
|         } |         } | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								Props/Models/Search/ProductListingInfo.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Props/Models/Search/ProductListingInfo.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | using System; | ||||||
|  | using Props.Shop.Framework; | ||||||
|  |  | ||||||
|  | namespace Props.Models.Search | ||||||
|  | { | ||||||
|  |     public class ProductListingInfo | ||||||
|  |     { | ||||||
|  |         public int Id { get; set; } | ||||||
|  |  | ||||||
|  |         public uint Hits { get; set; } | ||||||
|  |  | ||||||
|  |         public DateTime LastUpdated { get; set; } | ||||||
|  |  | ||||||
|  |         public string ProductUrl { get; set; } | ||||||
|  |  | ||||||
|  |         public string ProductName { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,23 +2,27 @@ using System; | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  | using Props.Models.User; | ||||||
| using Props.Shop.Framework; | using Props.Shop.Framework; | ||||||
| 
 | 
 | ||||||
| namespace Props.Models | namespace Props.Models.Search | ||||||
| { | { | ||||||
|     public class SearchOutline |     public class SearchOutline | ||||||
|     { |     { | ||||||
|         public int Id { get; set; } |         public int Id { get; set; } | ||||||
|  | 
 | ||||||
|         [Required] |         [Required] | ||||||
|         public string ApplicationUserId { get; set; } |         public string ApplicationUserId { get; set; } | ||||||
| 
 | 
 | ||||||
|         public Filters Filters { get; set; } |         [Required] | ||||||
|         public int MaxResults { get; set; } = 100; |         public virtual ApplicationUser ApplicationUser { get; set; } | ||||||
|  | 
 | ||||||
|  |         public Filters Filters { get; set; } = new Filters(); | ||||||
| 
 | 
 | ||||||
|         [Required] |         [Required] | ||||||
|         public ShopToggler ShopStates { get; set; } = new ShopToggler(); |         public ShopsDisabled Disabled { get; set; } = new ShopsDisabled(); | ||||||
| 
 | 
 | ||||||
|         public sealed class ShopToggler : HashSet<string> |         public sealed class ShopsDisabled : HashSet<string> | ||||||
|         { |         { | ||||||
|             public int TotalShops { get; set; } |             public int TotalShops { get; set; } | ||||||
|             public bool this[string name] |             public bool this[string name] | ||||||
| @@ -41,9 +45,9 @@ namespace Props.Models | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             public ShopToggler Copy() |             public ShopsDisabled Copy() | ||||||
|             { |             { | ||||||
|                 ShopToggler copy = new ShopToggler(); |                 ShopsDisabled copy = new ShopsDisabled(); | ||||||
|                 copy.Union(this); |                 copy.Union(this); | ||||||
|                 return copy; |                 return copy; | ||||||
|             } |             } | ||||||
| @@ -63,9 +67,8 @@ namespace Props.Models | |||||||
|             SearchOutline other = (SearchOutline)obj; |             SearchOutline other = (SearchOutline)obj; | ||||||
|             return |             return | ||||||
|                 Id == other.Id && |                 Id == other.Id && | ||||||
|                 MaxResults == other.MaxResults && |  | ||||||
|                 Filters.Equals(other.Filters) && |                 Filters.Equals(other.Filters) && | ||||||
|                 ShopStates.Equals(other.ShopStates); |                 Disabled.Equals(other.Disabled); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public override int GetHashCode() |         public override int GetHashCode() | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
|  | using Props.Models.User; | ||||||
|  |  | ||||||
| namespace Props.Shared.Models.User | namespace Props.Shared.Models.User | ||||||
| { | { | ||||||
| @@ -9,6 +10,9 @@ namespace Props.Shared.Models.User | |||||||
|         [Required] |         [Required] | ||||||
|         public string ApplicationUserId { get; set; } |         public string ApplicationUserId { get; set; } | ||||||
|  |  | ||||||
|  |         [Required] | ||||||
|  |         public virtual ApplicationUser ApplicationUser { get; set; } | ||||||
|  |  | ||||||
|         public bool EnableSearchHistory { get; set; } = true; |         public bool EnableSearchHistory { get; set; } = true; | ||||||
|         public bool DarkMode { get; set; } = false; |         public bool DarkMode { get; set; } = false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,30 +4,30 @@ using System.ComponentModel.DataAnnotations; | |||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.AspNetCore.Identity; | using Microsoft.AspNetCore.Identity; | ||||||
|  | using Props.Models.Search; | ||||||
| using Props.Shared.Models.User; | using Props.Shared.Models.User; | ||||||
|  |  | ||||||
| namespace Props.Models.User | namespace Props.Models.User | ||||||
| { | { | ||||||
|     public class ApplicationUser : IdentityUser |     public class ApplicationUser : IdentityUser | ||||||
|     { |     { | ||||||
|         [Required] |         public virtual ISet<SearchOutline> SearchOutlines { get; set; } | ||||||
|         public virtual SearchOutline SearchOutline { get; private set; } |  | ||||||
|         [Required] |         [Required] | ||||||
|         public virtual ResultsPreferences ResultsPreferences { get; private set; } |         public virtual ResultsPreferences ResultsPreferences { get; private set; } | ||||||
|  |  | ||||||
|         [Required] |         [Required] | ||||||
|         public virtual ApplicationPreferences ApplicationPreferences { get; private set; } |         public virtual ApplicationPreferences ApplicationPreferences { get; private set; } | ||||||
|  |  | ||||||
|  |         // TODO: Write project system. | ||||||
|         public ApplicationUser() |         public ApplicationUser() | ||||||
|         { |         { | ||||||
|             SearchOutline = new SearchOutline(); |  | ||||||
|             ResultsPreferences = new ResultsPreferences(); |             ResultsPreferences = new ResultsPreferences(); | ||||||
|             ApplicationPreferences = new ApplicationPreferences(); |             ApplicationPreferences = new ApplicationPreferences(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public ApplicationUser(SearchOutline searchOutline, ResultsPreferences resultsPreferences, ApplicationPreferences applicationPreferences) |         public ApplicationUser(ResultsPreferences resultsPreferences, ApplicationPreferences applicationPreferences) | ||||||
|         { |         { | ||||||
|             this.SearchOutline = searchOutline; |  | ||||||
|             this.ResultsPreferences = resultsPreferences; |             this.ResultsPreferences = resultsPreferences; | ||||||
|             this.ApplicationPreferences = applicationPreferences; |             this.ApplicationPreferences = applicationPreferences; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -3,18 +3,27 @@ using System.Collections.Generic; | |||||||
| using System.ComponentModel.DataAnnotations; | using System.ComponentModel.DataAnnotations; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text.Json; | using System.Text.Json; | ||||||
|  | using Props.Models.User; | ||||||
|  |  | ||||||
| namespace Props.Models | namespace Props.Models | ||||||
| { | { | ||||||
|     public class ResultsPreferences |     public class ResultsPreferences | ||||||
|     { |     { | ||||||
|         public int Id { get; set; } |         public int Id { get; set; } | ||||||
|  |  | ||||||
|         [Required] |         [Required] | ||||||
|         public string ApplicationUserId { get; set; } |         public string ApplicationUserId { get; set; } | ||||||
|  |  | ||||||
|  |         [Required] | ||||||
|  |         public virtual ApplicationUser ApplicationUser { get; set; } | ||||||
|  |  | ||||||
|  |  | ||||||
|         [Required] |         [Required] | ||||||
|         public IList<Category> Order { get; set; } |         public IList<Category> Order { get; set; } | ||||||
|  |  | ||||||
|  |         [Required] | ||||||
|  |         public string ProfileName { get; set; } | ||||||
|  |  | ||||||
|         public ResultsPreferences() |         public ResultsPreferences() | ||||||
|         { |         { | ||||||
|             Order = new List<Category>(Enum.GetValues<Category>().Length); |             Order = new List<Category>(Enum.GetValues<Category>().Length); | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								Props/Options/ModulesOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Props/Options/ModulesOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | namespace Props.Options | ||||||
|  | { | ||||||
|  |     public class ModulesOptions | ||||||
|  |     { | ||||||
|  |         public const string Modules = "Modules"; | ||||||
|  |         public string ShopsDir { get; set; } | ||||||
|  |         public bool RecursiveLoad { get; set; } | ||||||
|  |         public int MaxResults { get; set; } | ||||||
|  |         public string ShopRegex { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| <section class="jumbotron d-flex flex-column align-items-center"> | <section class="jumbotron d-flex flex-column align-items-center"> | ||||||
|     <div> |     <div> | ||||||
|         <img alt="Props logo" src="./images/logo.svg" class="img-fluid" style="max-height: 540px;" /> |         <img alt="Props logo" src="~/images/logo.svg" class="img-fluid" style="max-height: 540px;" asp-append-version="true" /> | ||||||
|     </div> |     </div> | ||||||
|     <div class="text-center px-3 my-2 concise"> |     <div class="text-center px-3 my-2 concise"> | ||||||
|         <h1 class="my-2">Props</h1> |         <h1 class="my-2">Props</h1> | ||||||
|   | |||||||
| @@ -4,13 +4,107 @@ | |||||||
|     ViewData["Specific"] = "Search"; |     ViewData["Specific"] = "Search"; | ||||||
| } | } | ||||||
|  |  | ||||||
| <div class="container d-flex flex-column align-items-center"> | <div class="container"> | ||||||
|     <form class="my-4" style="width: 720px;"> |     <div class="my-4 less-concise mx-auto"> | ||||||
|         <div class="input-group"> |         <div class="input-group"> | ||||||
|             <input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn"> |             <input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn" id="search-bar"> | ||||||
|             <input type="checkbox" class="btn-check" id="config-check-toggle" autocomplete="off"> |             <input type="checkbox" class="btn-check" id="config-check-toggle" autocomplete="off"> | ||||||
|             <label class="btn btn-outline-secondary" for="config-check-toggle"><i class="bi bi-sliders"></i></label> |             <label class="btn btn-outline-secondary" for="config-check-toggle"><i class="bi bi-sliders"></i></label> | ||||||
|             <button class="btn btn-outline-primary" type="button" id="search-btn">Search</button> |             <button class="btn btn-outline-primary" type="button" id="search-btn">Search</button> | ||||||
|         </div> |         </div> | ||||||
|     </form> |  | ||||||
|     </div> |     </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <div class="tear d-none" id="configuration"> | ||||||
|  |     <div class="container invisible"> | ||||||
|  |         <h2 class="my-2">Configuration</h2> | ||||||
|  |         <div class="row justify-content-md-center"> | ||||||
|  |             <section class="col-lg"> | ||||||
|  |                 <h3>Price</h3> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <label for="max-price" class="form-label">Maximum Price</label> | ||||||
|  |                     <div class="input-group"> | ||||||
|  |                         <div class="input-group-text"> | ||||||
|  |                             <input class="form-check-input mt-0" type="checkbox" id="max-price-enabled"> | ||||||
|  |                         </div> | ||||||
|  |                         <span class="input-group-text">$</span> | ||||||
|  |                         <input type="number" class="form-control" min="0" id="max-price"> | ||||||
|  |                         <span class="input-group-text">.00</span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <label for="min-price" class="form-label">Minimum Price</label> | ||||||
|  |                     <div class="input-group"> | ||||||
|  |                         <span class="input-group-text">$</span> | ||||||
|  |                         <input type="number" class="form-control" min="0" id="min-price"> | ||||||
|  |                         <span class="input-group-text">.00</span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <label for="max-shipping" class="form-label">Maximum Shipping Fee</label> | ||||||
|  |                     <div class="input-group"> | ||||||
|  |                         <div class="input-group-text"> | ||||||
|  |                             <input class="form-check-input mt-0" type="checkbox" id="max-shipping-enabled"> | ||||||
|  |                         </div> | ||||||
|  |                         <span class="input-group-text">$</span> | ||||||
|  |                         <input type="number" class="form-control" min="0" id="max-shipping"> | ||||||
|  |                         <span class="input-group-text">.00</span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <div class="form-check"> | ||||||
|  |                         <input class="form-check-input" type="checkbox" id="keep-unknown-shipping"> | ||||||
|  |                         <label class="form-check-label" for="keep-unknown-shipping">Keep Unknown Shipping</label> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </section> | ||||||
|  |             <section class="col-lg"> | ||||||
|  |                 <h3>Metrics</h3> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <label for="min-purchases" class="form-label">Minimum Purchases</label> | ||||||
|  |                     <div class="input-group"> | ||||||
|  |                         <input type="number" class="form-control" min="0" id="min-purchases"> | ||||||
|  |                         <span class="input-group-text">Purchases</span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <div class="form-check"> | ||||||
|  |                         <input class="form-check-input" type="checkbox" id="keep-unknown-purchases"> | ||||||
|  |                         <label class="form-check-label" for="keep-unknown-purchases">Keep Unknown Purchases</label> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <label for="min-reviews" class="form-label">Minimum Reviews</label> | ||||||
|  |                     <div class="input-group"> | ||||||
|  |                         <input type="number" class="form-control" min="0" id="min-reviews"> | ||||||
|  |                         <span class="input-group-text">Reviews</span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <div class="form-check"> | ||||||
|  |                         <input class="form-check-input" type="checkbox" id="keep-unknown-reviews"> | ||||||
|  |                         <label class="form-check-label" for="keep-unknown-reviews">Keep Unknown Number of Reviews</label> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-1"> | ||||||
|  |                     <label for="min-rating" class="form-label">Minimum Rating</label> | ||||||
|  |                     <input type="range" class="form-range" id="min-rating" min="0" max="100" step="1"> | ||||||
|  |                     <div id="min-rating-display" class="form-text"></div> | ||||||
|  |                 </div> | ||||||
|  |                 <div class="mb-3"> | ||||||
|  |                     <div class="form-check"> | ||||||
|  |                         <input class="form-check-input" type="checkbox" id="keep-unrated"> | ||||||
|  |                         <label class="form-check-label" for="keep-unrated">Keep Minimum Rating</label> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </section> | ||||||
|  |             <section class="col-md"> | ||||||
|  |                 <h3>Shops Enabled</h3> | ||||||
|  |                 <div class="mb-3 px-3" id="shop-checkboxes"> | ||||||
|  |                 </div> | ||||||
|  |             </section> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | @* TODO: Add results display and default results display *@ | ||||||
| @@ -4,6 +4,6 @@ namespace Props.Pages | |||||||
| { | { | ||||||
|     public class SearchModel : PageModel |     public class SearchModel : PageModel | ||||||
|     { |     { | ||||||
|  |         // TODO: Complete the search model. | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -11,10 +11,6 @@ | |||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|     <title>@ViewData["Title"] - Props</title> |     <title>@ViewData["Title"] - Props</title> | ||||||
|     <script src="~/js/site.js" asp-append-version="true"></script> |     <script src="~/js/site.js" asp-append-version="true"></script> | ||||||
|     @if (!string.IsNullOrEmpty((ViewData["Specific"] as string))) |  | ||||||
|     { |  | ||||||
|         <script src="@($"~/js/specific/{(ViewData["Specific"])}.js")" asp-append-version="true"></script> |  | ||||||
|     } |  | ||||||
| </head> | </head> | ||||||
|  |  | ||||||
| <body class="theme-light"> | <body class="theme-light"> | ||||||
| @@ -67,6 +63,10 @@ | |||||||
|         © 2021 - Props - <a asp-area="" asp-page="/Privacy">Privacy</a> |         © 2021 - Props - <a asp-area="" asp-page="/Privacy">Privacy</a> | ||||||
|     </footer> |     </footer> | ||||||
|  |  | ||||||
|  |     @if (!string.IsNullOrEmpty((ViewData["Specific"] as string))) | ||||||
|  |     { | ||||||
|  |         <script src="@($"~/js/specific/{(ViewData["Specific"])}.js")" asp-append-version="true"></script> | ||||||
|  |     } | ||||||
|     @await RenderSectionAsync("Scripts", required: false) |     @await RenderSectionAsync("Scripts", required: false) | ||||||
| </body> | </body> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,13 +3,13 @@ using Newtonsoft.Json.Linq; | |||||||
| 
 | 
 | ||||||
| namespace Props.Services.Content | namespace Props.Services.Content | ||||||
| { | { | ||||||
|     public class ContentManager<Page> : IContentManager<Page> |     public class CachedContentManager<TPage> : IContentManager<TPage> | ||||||
|     { |     { | ||||||
|         private dynamic data; |         private dynamic data; | ||||||
|         private readonly string directory; |         private readonly string directory; | ||||||
|         private readonly string fileName; |         private readonly string fileName; | ||||||
| 
 | 
 | ||||||
|         dynamic IContentManager<Page>.Json |         dynamic IContentManager<TPage>.Json | ||||||
|         { |         { | ||||||
|             get |             get | ||||||
|             { |             { | ||||||
| @@ -18,10 +18,10 @@ namespace Props.Services.Content | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public ContentManager(string directory = "content") |         public CachedContentManager(string directory = "content") | ||||||
|         { |         { | ||||||
|             this.directory = directory; |             this.directory = directory; | ||||||
|             this.fileName = typeof(Page).Name.Replace("Model", "") + ".json"; |             this.fileName = typeof(TPage).Name.Replace("Model", "") + ".json"; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										14
									
								
								Props/Services/Modules/IShopManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Props/Services/Modules/IShopManager.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | using System.Collections; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Props.Models.Search; | ||||||
|  | using Props.Shop.Framework; | ||||||
|  |  | ||||||
|  | namespace Props.Services.Modules | ||||||
|  | { | ||||||
|  |     public interface IShopManager | ||||||
|  |     { | ||||||
|  |         public IEnumerable<string> AvailableShops(); | ||||||
|  |         public Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								Props/Services/Modules/LocalShopManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Props/Services/Modules/LocalShopManager.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.IO; | ||||||
|  | using System.Reflection; | ||||||
|  | using System.Text.RegularExpressions; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | using Props.Models.Search; | ||||||
|  | using Props.Options; | ||||||
|  | using Props.Shop.Framework; | ||||||
|  |  | ||||||
|  | namespace Props.Services.Modules | ||||||
|  | { | ||||||
|  |     public class LocalShopManager : IShopManager | ||||||
|  |     { | ||||||
|  |         private ILogger<LocalShopManager> logger; | ||||||
|  |         private Dictionary<string, IShop> shops; | ||||||
|  |         private ModulesOptions options; | ||||||
|  |         private IConfiguration configuration; | ||||||
|  |         public LocalShopManager(IConfiguration configuration, ILogger<LocalShopManager> logger) | ||||||
|  |         { | ||||||
|  |             this.configuration = configuration; | ||||||
|  |             this.logger = logger; | ||||||
|  |             options = configuration.GetSection(ModulesOptions.Modules).Get<ModulesOptions>(); | ||||||
|  |  | ||||||
|  |             shops = new Dictionary<string, IShop>(); | ||||||
|  |             foreach (IShop shop in LoadShops(options.ShopsDir, options.ShopRegex, options.RecursiveLoad)) | ||||||
|  |             { | ||||||
|  |                 if (!shops.TryAdd(shop.ShopName, shop)) | ||||||
|  |                 { | ||||||
|  |                     logger.LogWarning("Duplicate shop {0} detected. Ignoring the latter.", shop.ShopName); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public IEnumerable<string> AvailableShops() | ||||||
|  |         { | ||||||
|  |             return shops.Keys; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public async Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline) | ||||||
|  |         { | ||||||
|  |             List<ProductListing> results = new List<ProductListing>(); | ||||||
|  |             foreach (string shopName in shops.Keys) | ||||||
|  |             { | ||||||
|  |                 if (!searchOutline.Disabled[shopName]) | ||||||
|  |                 { | ||||||
|  |                     int amount = 0; | ||||||
|  |                     await foreach (ProductListing product in shops[shopName].Search(query, searchOutline.Filters)) | ||||||
|  |                     { | ||||||
|  |                         if (searchOutline.Filters.Validate(product)) | ||||||
|  |                         { | ||||||
|  |                             amount += 1; | ||||||
|  |                             results.Add(product); | ||||||
|  |                         } | ||||||
|  |                         if (amount >= options.MaxResults) break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return results; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private IEnumerable<IShop> LoadShops(string shopsDir, string shopRegex, bool recursiveLoad) | ||||||
|  |         { | ||||||
|  |             Stack<string> directories = new Stack<string>(); | ||||||
|  |             directories.Push(shopsDir); | ||||||
|  |             string currentDirectory = null; | ||||||
|  |             while (directories.TryPop(out currentDirectory)) | ||||||
|  |             { | ||||||
|  |                 if (recursiveLoad) | ||||||
|  |                 { | ||||||
|  |                     foreach (string dir in Directory.EnumerateDirectories(currentDirectory)) | ||||||
|  |                     { | ||||||
|  |                         directories.Push(dir); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 foreach (string file in Directory.EnumerateFiles(currentDirectory)) | ||||||
|  |                 { | ||||||
|  |                     if (Path.GetExtension(file).Equals(".dll") && Regex.IsMatch(file, shopRegex)) | ||||||
|  |                     { | ||||||
|  |                         ShopAssemblyLoadContext context = new ShopAssemblyLoadContext(file); | ||||||
|  |                         Assembly assembly = context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(file))); | ||||||
|  |                         int success = 0; | ||||||
|  |                         foreach (Type type in assembly.GetTypes()) | ||||||
|  |                         { | ||||||
|  |                             if (typeof(IShop).IsAssignableFrom(type)) | ||||||
|  |                             { | ||||||
|  |                                 IShop shop = Activator.CreateInstance(type) as IShop; | ||||||
|  |                                 if (shop != null) | ||||||
|  |                                 { | ||||||
|  |                                     success += 1; | ||||||
|  |                                     yield return shop; | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         if (success == 0) | ||||||
|  |                         { | ||||||
|  |                             logger.LogWarning("There were no shops found within the assembly at path \"{0}\".", file); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ using System; | |||||||
| using System.Reflection; | using System.Reflection; | ||||||
| using System.Runtime.Loader; | using System.Runtime.Loader; | ||||||
| using Microsoft.Extensions.DependencyModel; | using Microsoft.Extensions.DependencyModel; | ||||||
|  | using Props.Shop.Framework; | ||||||
|  |  | ||||||
| namespace Props.Services.Modules | namespace Props.Services.Modules | ||||||
| { | { | ||||||
| @@ -16,6 +17,7 @@ namespace Props.Services.Modules | |||||||
|  |  | ||||||
|         protected override Assembly Load(AssemblyName assemblyName) |         protected override Assembly Load(AssemblyName assemblyName) | ||||||
|         { |         { | ||||||
|  |             if (assemblyName.FullName.Equals(typeof(IShop).Assembly.FullName)) return null; | ||||||
|             string assemblyPath = resolver.ResolveAssemblyToPath(assemblyName); |             string assemblyPath = resolver.ResolveAssemblyToPath(assemblyName); | ||||||
|             return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null; |             return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ using Microsoft.Extensions.Hosting; | |||||||
| using Props.Data; | using Props.Data; | ||||||
| using Props.Models.User; | using Props.Models.User; | ||||||
| using Props.Services.Content; | using Props.Services.Content; | ||||||
|  | using Props.Services.Modules; | ||||||
|  |  | ||||||
| namespace Props | namespace Props | ||||||
| { | { | ||||||
| @@ -53,7 +54,8 @@ namespace Props | |||||||
|                 .AddEntityFrameworkStores<ApplicationDbContext>(); |                 .AddEntityFrameworkStores<ApplicationDbContext>(); | ||||||
|             services.AddRazorPages(); |             services.AddRazorPages(); | ||||||
|  |  | ||||||
|             services.AddSingleton(typeof(IContentManager<>), typeof(ContentManager<>)); |             services.AddSingleton(typeof(IContentManager<>), typeof(CachedContentManager<>)); | ||||||
|  |             services.AddSingleton<IShopManager, LocalShopManager>(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. |         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | ||||||
| @@ -75,6 +77,7 @@ namespace Props | |||||||
|  |  | ||||||
|             app.UseEndpoints(endpoints => |             app.UseEndpoints(endpoints => | ||||||
|             { |             { | ||||||
|  |                 endpoints.MapControllers(); | ||||||
|                 endpoints.MapRazorPages(); |                 endpoints.MapRazorPages(); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -9,8 +9,11 @@ | |||||||
|       "Microsoft.Hosting.Lifetime": "Information" |       "Microsoft.Hosting.Lifetime": "Information" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "Shops": { |   "Modules": { | ||||||
|     "Path": "./shops" |     "ShopsDir": "./shops", | ||||||
|  |     "RecursiveLoad": "false", | ||||||
|  |     "MaxResults": "100", | ||||||
|  |     "ShopRegex": "Props\\.Shop\\.." | ||||||
|   }, |   }, | ||||||
|   "AllowedHosts": "*" |   "AllowedHosts": "*" | ||||||
| } | } | ||||||
							
								
								
									
										232
									
								
								Props/assets/images/logo-simplified.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								Props/assets/images/logo-simplified.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,232 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||||||
|  |  | ||||||
|  | <svg | ||||||
|  |    width="130.02815mm" | ||||||
|  |    height="183.73235mm" | ||||||
|  |    viewBox="0 0 130.02815 183.73235" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg5" | ||||||
|  |    inkscape:version="1.1 (c68e22c387, 2021-05-23)" | ||||||
|  |    sodipodi:docname="logo-simplified.svg" | ||||||
|  |    inkscape:export-filename="C:\Users\yunya\Documents\Props\Props\client\src\assets\images\logo.png" | ||||||
|  |    inkscape:export-xdpi="96" | ||||||
|  |    inkscape:export-ydpi="96" | ||||||
|  |    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||||
|  |    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <sodipodi:namedview | ||||||
|  |      id="namedview7" | ||||||
|  |      pagecolor="#ffffff" | ||||||
|  |      bordercolor="#666666" | ||||||
|  |      borderopacity="1.0" | ||||||
|  |      inkscape:pageshadow="2" | ||||||
|  |      inkscape:pageopacity="0.0" | ||||||
|  |      inkscape:pagecheckerboard="0" | ||||||
|  |      inkscape:document-units="mm" | ||||||
|  |      showgrid="false" | ||||||
|  |      inkscape:snap-global="true" | ||||||
|  |      inkscape:zoom="0.4734482" | ||||||
|  |      inkscape:cx="11.616899" | ||||||
|  |      inkscape:cy="186.92647" | ||||||
|  |      inkscape:window-width="1920" | ||||||
|  |      inkscape:window-height="1027" | ||||||
|  |      inkscape:window-x="-8" | ||||||
|  |      inkscape:window-y="-8" | ||||||
|  |      inkscape:window-maximized="1" | ||||||
|  |      inkscape:current-layer="layer1" /> | ||||||
|  |   <defs | ||||||
|  |      id="defs2" /> | ||||||
|  |   <g | ||||||
|  |      inkscape:label="Layer 1" | ||||||
|  |      inkscape:groupmode="layer" | ||||||
|  |      id="layer1" | ||||||
|  |      transform="translate(-40.717182,-65.583996)"> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#51b6bf;fill-opacity:1;stroke:none;stroke-width:0.394384" | ||||||
|  |        id="rect846" | ||||||
|  |        width="130.02815" | ||||||
|  |        height="180.11604" | ||||||
|  |        x="40.717182" | ||||||
|  |        y="69.200294" | ||||||
|  |        ry="7.2278728" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.355205" | ||||||
|  |        id="rect6139" | ||||||
|  |        width="114.73653" | ||||||
|  |        height="165.58026" | ||||||
|  |        x="48.362995" | ||||||
|  |        y="76.468185" | ||||||
|  |        ry="6.6445661" /> | ||||||
|  |     <g | ||||||
|  |        id="g20855" | ||||||
|  |        transform="translate(0,-4.5861139)"> | ||||||
|  |       <rect | ||||||
|  |          style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect4822-1" | ||||||
|  |          width="8.1061144" | ||||||
|  |          height="8.1061144" | ||||||
|  |          x="56.92775" | ||||||
|  |          y="113.97274" | ||||||
|  |          ry="1.3718038" /> | ||||||
|  |       <rect | ||||||
|  |          style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.86051;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect5136-7" | ||||||
|  |          width="42.169209" | ||||||
|  |          height="3.4690557" | ||||||
|  |          x="69.150803" | ||||||
|  |          y="116.29127" | ||||||
|  |          ry="1.7345278" /> | ||||||
|  |     </g> | ||||||
|  |     <g | ||||||
|  |        id="g20859" | ||||||
|  |        transform="translate(0,-7.0555611)"> | ||||||
|  |       <rect | ||||||
|  |          style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect4822-14" | ||||||
|  |          width="8.1061144" | ||||||
|  |          height="8.1061144" | ||||||
|  |          x="56.92775" | ||||||
|  |          y="127.76641" | ||||||
|  |          ry="1.3718038" /> | ||||||
|  |       <rect | ||||||
|  |          style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.43107;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect5136-7-2" | ||||||
|  |          width="21.986813" | ||||||
|  |          height="3.9363635" | ||||||
|  |          x="68.953468" | ||||||
|  |          y="129.85129" | ||||||
|  |          ry="1.9681817" /> | ||||||
|  |     </g> | ||||||
|  |     <g | ||||||
|  |        id="g20845" | ||||||
|  |        transform="translate(0,-2.1166667)"> | ||||||
|  |       <rect | ||||||
|  |          style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect4822" | ||||||
|  |          width="8.1061144" | ||||||
|  |          height="8.1061144" | ||||||
|  |          x="56.92775" | ||||||
|  |          y="100.17907" | ||||||
|  |          ry="1.3718038" /> | ||||||
|  |       <rect | ||||||
|  |          style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.59414;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect5136" | ||||||
|  |          width="28.571259" | ||||||
|  |          height="3.7589138" | ||||||
|  |          x="69.028404" | ||||||
|  |          y="102.35267" | ||||||
|  |          ry="1.8794569" /> | ||||||
|  |       <rect | ||||||
|  |          style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.50408;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect5136-5" | ||||||
|  |          width="24.788029" | ||||||
|  |          height="3.8569105" | ||||||
|  |          x="102.89133" | ||||||
|  |          y="102.30367" | ||||||
|  |          ry="1.9284552" /> | ||||||
|  |     </g> | ||||||
|  |     <g | ||||||
|  |        id="g20863" | ||||||
|  |        transform="translate(0,-9.5250005)"> | ||||||
|  |       <rect | ||||||
|  |          style="fill:none;fill-opacity:1;stroke:#3b3485;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect4822-1-4" | ||||||
|  |          width="8.1061144" | ||||||
|  |          height="8.1061144" | ||||||
|  |          x="56.92775" | ||||||
|  |          y="141.56007" | ||||||
|  |          ry="1.3718038" /> | ||||||
|  |       <rect | ||||||
|  |          style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:1.57172;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |          id="rect5728" | ||||||
|  |          width="27.594185" | ||||||
|  |          height="3.7833092" | ||||||
|  |          x="69.018089" | ||||||
|  |          y="143.72148" | ||||||
|  |          ry="1.8916546" /> | ||||||
|  |     </g> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#202042;fill-opacity:1;stroke:none;stroke-width:0.23824" | ||||||
|  |        id="rect1392" | ||||||
|  |        width="53.152565" | ||||||
|  |        height="18.233921" | ||||||
|  |        x="-132.30754" | ||||||
|  |        y="-83.817917" | ||||||
|  |        ry="3.4788733" | ||||||
|  |        transform="scale(-1)" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.22336;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6412" | ||||||
|  |        width="73.657707" | ||||||
|  |        height="2.8362327" | ||||||
|  |        x="56.336582" | ||||||
|  |        y="201.33577" | ||||||
|  |        ry="1.4181163" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.23876;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6414" | ||||||
|  |        width="69.712616" | ||||||
|  |        height="3.0383809" | ||||||
|  |        x="56.344284" | ||||||
|  |        y="209.24466" | ||||||
|  |        ry="1.5191904" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.42357;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6416" | ||||||
|  |        width="86.988022" | ||||||
|  |        height="2.8535743" | ||||||
|  |        x="56.436687" | ||||||
|  |        y="217.23825" | ||||||
|  |        ry="1.4267871" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#8383bc;fill-opacity:1;stroke:none;stroke-width:2.38882;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6418" | ||||||
|  |        width="83.495445" | ||||||
|  |        height="2.8883159" | ||||||
|  |        x="56.419315" | ||||||
|  |        y="225.12207" | ||||||
|  |        ry="1.444158" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:3.0459;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6295-8" | ||||||
|  |        width="57.490276" | ||||||
|  |        height="6.8199105" | ||||||
|  |        x="76.986122" | ||||||
|  |        y="87.608482" | ||||||
|  |        ry="3.4099553" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:2.58155;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6490" | ||||||
|  |        width="41.297459" | ||||||
|  |        height="6.8199105" | ||||||
|  |        x="85.082527" | ||||||
|  |        y="188.09093" | ||||||
|  |        ry="3.4099553" /> | ||||||
|  |     <rect | ||||||
|  |        style="fill:#2f3898;fill-opacity:1;stroke:none;stroke-width:1.87563;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6490-3" | ||||||
|  |        width="21.8001" | ||||||
|  |        height="6.8199105" | ||||||
|  |        x="94.831207" | ||||||
|  |        y="151.63762" | ||||||
|  |        ry="3.4099553" /> | ||||||
|  |     <rect | ||||||
|  |        style="opacity:1;fill:#2bc8d7;fill-opacity:1;stroke:none;stroke-width:1.63579;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6492" | ||||||
|  |        width="16.931116" | ||||||
|  |        height="13.580167" | ||||||
|  |        x="132.27409" | ||||||
|  |        y="201.23309" | ||||||
|  |        ry="2.6454868" /> | ||||||
|  |     <rect | ||||||
|  |        style="opacity:1;fill:#dcf8f6;fill-opacity:1;stroke:none;stroke-width:1.465;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | ||||||
|  |        id="rect6932" | ||||||
|  |        width="79.717339" | ||||||
|  |        height="19.929335" | ||||||
|  |        x="65.872589" | ||||||
|  |        y="161.02196" | ||||||
|  |        ry="5.6437054" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 7.6 KiB | 
| @@ -1,2 +1,2 @@ | |||||||
| import "../../node_modules/bootstrap/js/dist/collapse"; | import "~/node_modules/bootstrap/js/dist/collapse"; | ||||||
| import "simplebar"; | import "simplebar"; | ||||||
|   | |||||||
| @@ -1 +1,115 @@ | |||||||
| console.log("abc"); | import { apiHttp } from "~/assets/js/services/http.js"; | ||||||
|  |  | ||||||
|  | // All input fields. | ||||||
|  | let inputs = { | ||||||
|  |     maxPriceEnabled: document.getElementById("max-price-enabled"), | ||||||
|  |     maxShippingEnabled: document.getElementById("max-shipping-enabled"), | ||||||
|  |     minRating: document.getElementById("min-rating"), | ||||||
|  |     maxPrice: document.getElementById("max-price"), | ||||||
|  |     maxShipping: document.getElementById("max-shipping"), | ||||||
|  |     keepUnknownPurchases: document.getElementById("keep-unknown-purchases"), | ||||||
|  |     keepUnknownReviews: document.getElementById("keep-unknown-reviews"), | ||||||
|  |     keepUnknownShipping: document.getElementById("keep-unknown-shipping"), | ||||||
|  |     keepUnrated: document.getElementById("keep-unrated"), | ||||||
|  |     minPrice: document.getElementById("min-price"), | ||||||
|  |     minPurchases: document.getElementById("min-purchases"), | ||||||
|  |     minReviews: document.getElementById("min-reviews"), | ||||||
|  |     shopToggles: {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | async function main() { | ||||||
|  |     setupInteractiveBehavior(); | ||||||
|  |     await setupInitialValues((await apiHttp.get("/Search/Default/Outline/Filters")).data); | ||||||
|  |     await setupShopToggles((await apiHttp.get("/Search/Shops/Available")).data); | ||||||
|  |  | ||||||
|  |     document.getElementById("configuration").querySelector(".invisible").classList.remove("invisible"); // Load completed, show the UI. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function setupInteractiveBehavior() { | ||||||
|  |     document.getElementById("config-check-toggle").addEventListener("change", function () { | ||||||
|  |         let configElem = document.getElementById("configuration"); | ||||||
|  |         if (this.checked) { | ||||||
|  |             configElem.classList.remove("d-none"); | ||||||
|  |         } else { | ||||||
|  |             configElem.classList.add("d-none"); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     inputs.maxPriceEnabled.addEventListener("change", function () { | ||||||
|  |         inputs.maxPrice.disabled = !this.checked; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     inputs.maxShippingEnabled.addEventListener("change", function () { | ||||||
|  |         inputs.maxShipping.disabled = !this.checked; | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     inputs.minRating.addEventListener("input", function () { | ||||||
|  |         document.getElementById("min-rating-display").innerHTML = `Minimum rating: ${this.value}%`; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function setupInitialValues(filters) { | ||||||
|  |     inputs.maxShippingEnabled.checked = filters.enableMaxShippingFee; | ||||||
|  |     inputs.maxShippingEnabled.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.maxPriceEnabled.checked = filters.enableUpperPrice; | ||||||
|  |     inputs.maxPriceEnabled.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.keepUnknownPurchases.checked = filters.keepUnknownPurchaseCount; | ||||||
|  |     inputs.keepUnknownPurchases.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.keepUnknownReviews.checked = filters.keepUnknownReviewCount; | ||||||
|  |     inputs.keepUnknownReviews.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.keepUnknownShipping.checked = filters.keepUnknownShipping; | ||||||
|  |     inputs.keepUnknownShipping.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.keepUnrated.checked = filters.keepUnrated; | ||||||
|  |     inputs.keepUnrated.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.minPrice.value = filters.lowerPrice; | ||||||
|  |     inputs.minPrice.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.maxShipping.value = filters.maxShippingFee; | ||||||
|  |     inputs.maxShipping.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.minPurchases.value = filters.minPurchases; | ||||||
|  |     inputs.minPurchases.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.minRating.value = filters.minRating * 100; | ||||||
|  |     inputs.minRating.dispatchEvent(new Event("input")); | ||||||
|  |  | ||||||
|  |     inputs.minReviews.value = filters.minReviews; | ||||||
|  |     inputs.minReviews.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |     inputs.maxPrice.value = filters.upperPrice; | ||||||
|  |     inputs.maxPrice.dispatchEvent(new Event("change")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function setupShopToggles(availableShops) { | ||||||
|  |     let disabledShops = (await apiHttp.get("/Search/Default/Outline/DisabledShops")).data; | ||||||
|  |     let shopsElem = document.getElementById("shop-checkboxes"); | ||||||
|  |     availableShops.forEach(shopName => { | ||||||
|  |         let id = `${shopName}-enabled`; | ||||||
|  |         let shopLabelElem = document.createElement("label"); | ||||||
|  |         shopLabelElem.classList.add("form-check-label"); | ||||||
|  |         shopLabelElem.htmlFor = id; | ||||||
|  |         shopLabelElem.innerHTML = `Enable ${shopName}`; | ||||||
|  |  | ||||||
|  |         let shopCheckboxElem = document.createElement("input"); | ||||||
|  |         shopCheckboxElem.classList.add("form-check-input"); | ||||||
|  |         shopCheckboxElem.type = "checkbox"; | ||||||
|  |         shopCheckboxElem.id = id; | ||||||
|  |         shopCheckboxElem.checked = !disabledShops.includes(shopName); | ||||||
|  |         inputs.shopToggles[shopName] = shopCheckboxElem; | ||||||
|  |  | ||||||
|  |         let shopToggleElem = document.createElement("div"); | ||||||
|  |         shopToggleElem.classList.add("form-check"); | ||||||
|  |         shopToggleElem.appendChild(shopCheckboxElem); | ||||||
|  |         shopToggleElem.appendChild(shopLabelElem); | ||||||
|  |         shopsElem.appendChild(shopToggleElem); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main(); | ||||||
|  | // TODO: Animate configuration toggle. | ||||||
|   | |||||||
| @@ -1,3 +1,12 @@ | |||||||
| $themes: ( | $themes: ( | ||||||
|     "light": ("background": #f4f4f4, "navbar": #FFF8F8, "main": #BDF2D5, "footer": #F2F2F2,"sub": #F4FCFC, "bold": #647b9b, "text": #1A1A1A, "muted": #797a7e), |     "light": ( | ||||||
|  |         "background": #f4f4f4,  | ||||||
|  |         "navbar": #FFF8F8,  | ||||||
|  |         "main": #BDF2D5,  | ||||||
|  |         "footer": #F2F2F2,  | ||||||
|  |         "sub": #F4FCFC,  | ||||||
|  |         "bold": #647b9b,  | ||||||
|  |         "text": #1A1A1A,  | ||||||
|  |         "muted": #797a7e,  | ||||||
|  |     ), | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| @use "themer"; | @use "themer"; | ||||||
| @use "~/node_modules/bootstrap/scss/bootstrap"; | @use "~/node_modules/bootstrap/scss/bootstrap"; | ||||||
| @import "~/node_modules/bootstrap-icons/font/bootstrap-icons.css"; | @use "sass:color"; | ||||||
| @import "~/node_modules/simplebar/dist/simplebar.min.css"; |  | ||||||
|  |  | ||||||
| header > nav { | header > nav { | ||||||
|     @extend .navbar-expand-lg; |     @extend .navbar-expand-lg; | ||||||
| @@ -33,6 +32,19 @@ main { | |||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|  |  | ||||||
|  |     .tear { | ||||||
|  |         @extend .p-3; | ||||||
|  |         border-top: 1px; | ||||||
|  |         border-top-style: solid; | ||||||
|  |         border-bottom: 1px; | ||||||
|  |         border-bottom-style: solid; | ||||||
|  |         @include themer.themed { | ||||||
|  |             $tear: themer.color-of("background"); | ||||||
|  |             border-color: adjust-color($color: $tear, $lightness: -20%, $alpha: 1.0); | ||||||
|  |             background-color: adjust-color($color: $tear, $lightness: -5%, $alpha: 1.0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| footer { | footer { | ||||||
| @@ -65,6 +77,16 @@ footer { | |||||||
|             background-color: themer.color-of("sub"); |             background-color: themer.color-of("sub"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     &.sole { | ||||||
|  |         @include themer.themed { | ||||||
|  |             background-color: themer.color-of("main"); | ||||||
|  |             border-color: themer.color-of("sub"); | ||||||
|  |             border-top-style: solid; | ||||||
|  |             border-bottom-style: solid; | ||||||
|  |             border-width: 1em; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| .concise { | .concise { | ||||||
| @@ -113,3 +135,6 @@ body { | |||||||
|         color: themer.color-of("text"); |         color: themer.color-of("text"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @import "~/node_modules/bootstrap-icons/font/bootstrap-icons.css"; | ||||||
|  | @import "~/node_modules/simplebar/dist/simplebar.min.css"; | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								Props/babel.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Props/babel.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | module.exports = function (api) { | ||||||
|  |     api.cache(true); | ||||||
|  |  | ||||||
|  |     const presets = []; | ||||||
|  |     const plugins = []; | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         presets, | ||||||
|  |         plugins | ||||||
|  |     }; | ||||||
|  | }; | ||||||
							
								
								
									
										54
									
								
								Props/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										54
									
								
								Props/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -63,6 +63,23 @@ | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@babel/eslint-parser": { | ||||||
|  |       "version": "7.14.7", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz", | ||||||
|  |       "integrity": "sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ==", | ||||||
|  |       "requires": { | ||||||
|  |         "eslint-scope": "^5.1.1", | ||||||
|  |         "eslint-visitor-keys": "^2.1.0", | ||||||
|  |         "semver": "^6.3.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "semver": { | ||||||
|  |           "version": "6.3.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", | ||||||
|  |           "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@babel/generator": { |     "@babel/generator": { | ||||||
|       "version": "7.14.8", |       "version": "7.14.8", | ||||||
|       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", |       "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.8.tgz", | ||||||
| @@ -950,6 +967,28 @@ | |||||||
|         "@babel/helper-plugin-utils": "^7.14.5" |         "@babel/helper-plugin-utils": "^7.14.5" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "@babel/plugin-transform-runtime": { | ||||||
|  |       "version": "7.14.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.14.5.tgz", | ||||||
|  |       "integrity": "sha512-fPMBhh1AV8ZyneiCIA+wYYUH1arzlXR1UMcApjvchDhfKxhy2r2lReJv8uHEyihi4IFIGlr1Pdx7S5fkESDQsg==", | ||||||
|  |       "dev": true, | ||||||
|  |       "requires": { | ||||||
|  |         "@babel/helper-module-imports": "^7.14.5", | ||||||
|  |         "@babel/helper-plugin-utils": "^7.14.5", | ||||||
|  |         "babel-plugin-polyfill-corejs2": "^0.2.2", | ||||||
|  |         "babel-plugin-polyfill-corejs3": "^0.2.2", | ||||||
|  |         "babel-plugin-polyfill-regenerator": "^0.2.2", | ||||||
|  |         "semver": "^6.3.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "semver": { | ||||||
|  |           "version": "6.3.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", | ||||||
|  |           "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", | ||||||
|  |           "dev": true | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "@babel/plugin-transform-shorthand-properties": { |     "@babel/plugin-transform-shorthand-properties": { | ||||||
|       "version": "7.14.5", |       "version": "7.14.5", | ||||||
|       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", |       "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz", | ||||||
| @@ -1121,7 +1160,6 @@ | |||||||
|       "version": "7.14.8", |       "version": "7.14.8", | ||||||
|       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz", |       "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz", | ||||||
|       "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==", |       "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==", | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "regenerator-runtime": "^0.13.4" |         "regenerator-runtime": "^0.13.4" | ||||||
|       } |       } | ||||||
| @@ -2136,7 +2174,6 @@ | |||||||
|       "version": "5.1.1", |       "version": "5.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", |       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", | ||||||
|       "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", |       "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "esrecurse": "^4.3.0", |         "esrecurse": "^4.3.0", | ||||||
|         "estraverse": "^4.1.1" |         "estraverse": "^4.1.1" | ||||||
| @@ -2162,8 +2199,7 @@ | |||||||
|     "eslint-visitor-keys": { |     "eslint-visitor-keys": { | ||||||
|       "version": "2.1.0", |       "version": "2.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", |       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", | ||||||
|       "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", |       "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" | ||||||
|       "dev": true |  | ||||||
|     }, |     }, | ||||||
|     "espree": { |     "espree": { | ||||||
|       "version": "7.3.1", |       "version": "7.3.1", | ||||||
| @@ -2217,7 +2253,6 @@ | |||||||
|       "version": "4.3.0", |       "version": "4.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", |       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", | ||||||
|       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", |       "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "estraverse": "^5.2.0" |         "estraverse": "^5.2.0" | ||||||
|       }, |       }, | ||||||
| @@ -2225,16 +2260,14 @@ | |||||||
|         "estraverse": { |         "estraverse": { | ||||||
|           "version": "5.2.0", |           "version": "5.2.0", | ||||||
|           "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", |           "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", | ||||||
|           "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", |           "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" | ||||||
|           "dev": true |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "estraverse": { |     "estraverse": { | ||||||
|       "version": "4.3.0", |       "version": "4.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", |       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", | ||||||
|       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", |       "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" | ||||||
|       "dev": true |  | ||||||
|     }, |     }, | ||||||
|     "esutils": { |     "esutils": { | ||||||
|       "version": "2.0.3", |       "version": "2.0.3", | ||||||
| @@ -3227,8 +3260,7 @@ | |||||||
|     "regenerator-runtime": { |     "regenerator-runtime": { | ||||||
|       "version": "0.13.7", |       "version": "0.13.7", | ||||||
|       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", |       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", | ||||||
|       "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", |       "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" | ||||||
|       "dev": true |  | ||||||
|     }, |     }, | ||||||
|     "regenerator-transform": { |     "regenerator-transform": { | ||||||
|       "version": "0.14.5", |       "version": "0.14.5", | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
|   "license": "ISC", |   "license": "ISC", | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@babel/core": "^7.14.8", |     "@babel/core": "^7.14.8", | ||||||
|  |     "@babel/plugin-transform-runtime": "^7.14.5", | ||||||
|     "@babel/preset-env": "^7.14.8", |     "@babel/preset-env": "^7.14.8", | ||||||
|     "babel-eslint": "^10.1.0", |     "babel-eslint": "^10.1.0", | ||||||
|     "babel-loader": "^8.2.2", |     "babel-loader": "^8.2.2", | ||||||
| @@ -23,6 +24,8 @@ | |||||||
|     "webpack-cli": "^4.7.2" |     "webpack-cli": "^4.7.2" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@babel/eslint-parser": "^7.14.7", | ||||||
|  |     "@babel/runtime": "^7.14.8", | ||||||
|     "axios": "^0.21.1", |     "axios": "^0.21.1", | ||||||
|     "bootstrap": "^5.0.2", |     "bootstrap": "^5.0.2", | ||||||
|     "bootstrap-icons": "^1.5.0", |     "bootstrap-icons": "^1.5.0", | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								Props/shops/FuzzySharp.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Props/shops/FuzzySharp.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Props/shops/Newtonsoft.Json.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Props/shops/Newtonsoft.Json.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Props/shops/Props.Shop.Adafruit.dll
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Props/shops/Props.Shop.Adafruit.dll
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -31,7 +31,8 @@ let config = { | |||||||
|                 use: { |                 use: { | ||||||
|                     loader: "babel-loader", |                     loader: "babel-loader", | ||||||
|                     options: { |                     options: { | ||||||
|                         presets: ["@babel/preset-env"] |                         presets: ["@babel/preset-env"], | ||||||
|  |                         plugins: ["@babel/plugin-transform-runtime"] | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user