From 96f8c014f18ff2e702d021027684e4f50f756d81 Mon Sep 17 00:00:00 2001 From: "Collin M. Barrett" Date: Fri, 21 Sep 2018 19:57:01 -0500 Subject: [PATCH] Squashed commit of the following: commit 5624d5049d0efa99dbb2d7c8453c0c756eb283aa Author: Collin Barrett Date: Fri Sep 21 19:48:36 2018 -0500 move index to new endpoint for testing ref #505 commit e49f3b6312372078f9e57a451cfae4df06145597 Author: Collin Barrett Date: Fri Sep 21 19:40:29 2018 -0500 restore camelCase commit 94fa3c5f4ef1e47c80108c0648a5f3597d305ba7 Author: Collin Barrett Date: Fri Sep 21 19:20:38 2018 -0500 ignore nulls and empties from json commit 5bc704ffad168ae90cbe232161d3b205d8855b07 Author: collinbarrett Date: Fri Sep 21 12:40:25 2018 -0500 add maintainers commit 68bab5bb1c3394e960ec2695c6f51f5adace1cec Merge: 106edf5 71a3d75 Author: collinbarrett Date: Fri Sep 21 10:35:43 2018 -0500 Merge branch 'master' into refactor-api commit 106edf5b96d4d2296374ef60c8d31923316199d2 Author: Collin Barrett Date: Thu Sep 20 06:15:49 2018 -0500 wip commit 1387ef5836165f716b59df19ca6fca5959c65ee8 Author: Collin Barrett Date: Thu Sep 20 05:54:16 2018 -0500 wip commit fcebd19ecb583dd76112c0c4d2c1d9b1c2f0d766 Author: Collin Barrett Date: Wed Sep 19 21:11:47 2018 -0500 wip commit e9d78450e85120a0002d5a4f528aca6992d63646 Author: Collin Barrett Date: Wed Sep 19 18:23:08 2018 -0500 wip commit bb915dcfe605278a941e5ac0cfc50c363d744c41 Author: collinbarrett Date: Wed Sep 19 15:49:12 2018 -0500 RuleCount wip commit 8e1c0fffed9af0485dec07f33ee130d6b9ecb305 Author: collinbarrett Date: Wed Sep 19 15:17:28 2018 -0500 use IReadOnlyCollection per https://stackoverflow.com/questions/52368567/optimization-of-correlated-subqueries-with-automapper?noredirect=1#comment91770378_52368567 commit 6362d5891c728cf83bf2e3bdbb4b244c56aafdc9 Author: Collin Barrett Date: Wed Sep 19 05:38:54 2018 -0500 wip --- .../Extensions/ConfigureServicesCollection.cs | 9 +++- src/FilterLists.Api/FilterLists.Api.csproj | 1 + .../SkipEmptyContractResolver.cs | 35 ++++++++++++ .../V1/Controllers/BaseController.cs | 2 +- .../V1/Controllers/ListsController.cs | 13 ++++- .../FilterList/FilterListService.cs | 13 +++-- .../ListIndexRecordMappingProfile.cs | 53 +++++++++++++++++++ .../FilterList/Models/ListIndexRecord.cs | 21 ++++++++ 8 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 src/FilterLists.Api/SkipEmptyContractResolver.cs create mode 100644 src/FilterLists.Services/FilterList/MappingProfiles/ListIndexRecordMappingProfile.cs create mode 100644 src/FilterLists.Services/FilterList/Models/ListIndexRecord.cs diff --git a/src/FilterLists.Api/DependencyInjection/Extensions/ConfigureServicesCollection.cs b/src/FilterLists.Api/DependencyInjection/Extensions/ConfigureServicesCollection.cs index c007e0a60..4ffde532b 100644 --- a/src/FilterLists.Api/DependencyInjection/Extensions/ConfigureServicesCollection.cs +++ b/src/FilterLists.Api/DependencyInjection/Extensions/ConfigureServicesCollection.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.PlatformAbstractions; +using Newtonsoft.Json; using Swashbuckle.AspNetCore.Swagger; namespace FilterLists.Api.DependencyInjection.Extensions @@ -32,7 +33,13 @@ private static void ConfigureCookiePolicy(this IServiceCollection services) => }); private static void AddMvcCustom(this IServiceCollection services) => - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) + .AddJsonOptions(opts => + { + opts.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; + opts.SerializerSettings.ContractResolver = new SkipEmptyContractResolver(); + }); private static void AddRoutingCustom(this IServiceCollection services) => services.AddRouting(opts => opts.LowercaseUrls = true); diff --git a/src/FilterLists.Api/FilterLists.Api.csproj b/src/FilterLists.Api/FilterLists.Api.csproj index 4b1732982..1aec86167 100644 --- a/src/FilterLists.Api/FilterLists.Api.csproj +++ b/src/FilterLists.Api/FilterLists.Api.csproj @@ -35,6 +35,7 @@ + diff --git a/src/FilterLists.Api/SkipEmptyContractResolver.cs b/src/FilterLists.Api/SkipEmptyContractResolver.cs new file mode 100644 index 000000000..1c5e083a1 --- /dev/null +++ b/src/FilterLists.Api/SkipEmptyContractResolver.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections; +using System.Reflection; +using Humanizer; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace FilterLists.Api +{ + // REF: https://stackoverflow.com/a/18486790/2343739 + public class SkipEmptyContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + property.PropertyName = property.PropertyName.Camelize(); + var isDefaultValueIgnored = + ((property.DefaultValueHandling ?? DefaultValueHandling.Ignore) & DefaultValueHandling.Ignore) != 0; + if (!isDefaultValueIgnored || typeof(string).IsAssignableFrom(property.PropertyType) || + !typeof(IEnumerable).IsAssignableFrom(property.PropertyType)) + return property; + + bool NewShouldSerialize(object obj) + { + return !(property.ValueProvider.GetValue(obj) is ICollection collection) || collection.Count != 0; + } + + var oldShouldSerialize = property.ShouldSerialize; + property.ShouldSerialize = oldShouldSerialize != null + ? o => oldShouldSerialize(o) && NewShouldSerialize(o) + : (Predicate)NewShouldSerialize; + return property; + } + } +} \ No newline at end of file diff --git a/src/FilterLists.Api/V1/Controllers/BaseController.cs b/src/FilterLists.Api/V1/Controllers/BaseController.cs index 98859df44..7aff4293f 100644 --- a/src/FilterLists.Api/V1/Controllers/BaseController.cs +++ b/src/FilterLists.Api/V1/Controllers/BaseController.cs @@ -5,7 +5,7 @@ namespace FilterLists.Api.V1.Controllers { - [ApiVersion("1.0")] + [ApiVersion("1")] //TODO: use versioning without needing to manually specify in swagger-ui (https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/370) [Route("v{version:apiVersion}/[controller]")] [ResponseCache(Duration = 86400)] diff --git a/src/FilterLists.Api/V1/Controllers/ListsController.cs b/src/FilterLists.Api/V1/Controllers/ListsController.cs index 4d79e654a..03f707dba 100644 --- a/src/FilterLists.Api/V1/Controllers/ListsController.cs +++ b/src/FilterLists.Api/V1/Controllers/ListsController.cs @@ -23,9 +23,20 @@ public async Task Index() => return filterListService.GetAllSummariesAsync(); })); + /// + /// in development for https://github.com/collinbarrett/FilterLists/issues/505 + /// + [HttpGet] + [Route("alpha")] + public async Task Alpha() => + Json(await MemoryCache.GetOrCreate("ListsController_Alpha", entry => + { + entry.AbsoluteExpirationRelativeToNow = MemoryCacheExpirationDefault; + return filterListService.GetIndexAsync(); + })); + [HttpGet] [Route("{id}")] - //TODO: respond with appropriate exception if negative id queried public async Task GetById(int id) => Json(await MemoryCache.GetOrCreate("ListsController_GetById_" + id, entry => { diff --git a/src/FilterLists.Services/FilterList/FilterListService.cs b/src/FilterLists.Services/FilterList/FilterListService.cs index 8f651eea2..b60a964d9 100644 --- a/src/FilterLists.Services/FilterList/FilterListService.cs +++ b/src/FilterLists.Services/FilterList/FilterListService.cs @@ -5,12 +5,10 @@ using AutoMapper.QueryableExtensions; using FilterLists.Data; using FilterLists.Services.FilterList.Models; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; namespace FilterLists.Services.FilterList { - [UsedImplicitly] public class FilterListService : Service { public FilterListService(FilterListsDbContext dbContext, IConfigurationProvider mapConfig) @@ -19,7 +17,16 @@ public FilterListService(FilterListsDbContext dbContext, IConfigurationProvider } public async Task> GetAllSummariesAsync() => - await DbContext.FilterLists.OrderBy(l => l.Name).ProjectTo(MapConfig).ToListAsync(); + await DbContext.FilterLists + .OrderBy(l => l.Name) + .ProjectTo(MapConfig) + .ToListAsync(); + + public async Task> GetIndexAsync() => + await DbContext.FilterLists + .OrderBy(l => l.Name) + .ProjectTo(MapConfig) + .ToListAsync(); public async Task GetDetailsAsync(uint id) => await DbContext.FilterLists.ProjectTo(MapConfig) diff --git a/src/FilterLists.Services/FilterList/MappingProfiles/ListIndexRecordMappingProfile.cs b/src/FilterLists.Services/FilterList/MappingProfiles/ListIndexRecordMappingProfile.cs new file mode 100644 index 000000000..0e1992630 --- /dev/null +++ b/src/FilterLists.Services/FilterList/MappingProfiles/ListIndexRecordMappingProfile.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using FilterLists.Services.FilterList.Models; +using JetBrains.Annotations; + +namespace FilterLists.Services.FilterList.MappingProfiles +{ + [UsedImplicitly] + public class ListIndexRecordMappingProfile : Profile + { + public ListIndexRecordMappingProfile() => + CreateMap() + .ForMember(r => r.Id, + o => o.MapFrom(l => + (int)l.Id)) + .ForMember(r => r.LanguageIds, + o => o.MapFrom(l => + l.FilterListLanguages.Select(ll => (int)ll.LanguageId))) + .ForMember(r => r.MaintainerIds, + o => o.MapFrom(l => + l.FilterListMaintainers.Select(ll => (int)ll.MaintainerId))) + .ForMember(r => r.RuleCount, + o => o.MapFrom(l => + l.Snapshots + .Where(s => s.WasSuccessful) + .Select(s => (int?)s.SnapshotRules.Count) + .FirstOrDefault())) + .ForMember(r => r.SyntaxId, + o => o.MapFrom(l => + (int)l.SyntaxId)) + .ForMember(r => r.TagIds, + o => o.MapFrom(l => + l.FilterListTags.Select(lt => (int)lt.TagId))) + .ForMember(r => r.UpdatedDate, + o => o.MapFrom(l => + l.Snapshots + .Count(s => s.WasSuccessful && s.WasUpdated) >= 2 + ? l.Snapshots + .Where(s => s.WasSuccessful && s.WasUpdated) + .Select(s => s.CreatedDateUtc) + .OrderByDescending(c => c) + .FirstOrDefault() + : null)) + .ForMember(r => r.ViewUrlMirrors, + o => o.MapFrom(l => + l.ViewUrlMirror1 != null + ? l.ViewUrlMirror2 != null + ? new List {l.ViewUrlMirror1, l.ViewUrlMirror2} + : new List {l.ViewUrlMirror1} + : null)); + } +} \ No newline at end of file diff --git a/src/FilterLists.Services/FilterList/Models/ListIndexRecord.cs b/src/FilterLists.Services/FilterList/Models/ListIndexRecord.cs new file mode 100644 index 000000000..1f139d631 --- /dev/null +++ b/src/FilterLists.Services/FilterList/Models/ListIndexRecord.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace FilterLists.Services.FilterList.Models +{ + [UsedImplicitly] + public class ListIndexRecord + { + public int Id { get; set; } + public List LanguageIds { get; set; } + public List MaintainerIds { get; set; } + public string Name { get; set; } + public int? RuleCount { get; set; } + public int? SyntaxId { get; set; } + public List TagIds { get; set; } + public DateTime? UpdatedDate { get; set; } + public string ViewUrl { get; set; } + public List ViewUrlMirrors { get; set; } + } +} \ No newline at end of file