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