mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
general cleanup to entire sln
This commit is contained in:
parent
df8614da8c
commit
1968bf1707
44 changed files with 157 additions and 177 deletions
|
|
@ -8,8 +8,10 @@
|
|||
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/JsInspections/LanguageLevel/@EntryValue">ECMAScript 2015</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">Built-in: Full Cleanup</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/CONSTRUCTOR_OR_DESTRUCTOR_BODY/@EntryValue">ExpressionBody</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">ExpressionBody</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
|
|
|
|||
|
|
@ -2837,7 +2837,8 @@
|
|||
{
|
||||
"id": 148,
|
||||
"chatUrl": null,
|
||||
"description": "I make supplemental filters in Adblock Plus to block garbage on webpages not blocked by other lists. I use many regular expressions, a few domain filters, but no CSS filters.",
|
||||
"description":
|
||||
"I make supplemental filters in Adblock Plus to block garbage on webpages not blocked by other lists. I use many regular expressions, a few domain filters, but no CSS filters.",
|
||||
"descriptionSourceUrl": "https://satterly.neocities.org/abp_filters.html",
|
||||
"discontinuedDate": null,
|
||||
"donateUrl": null,
|
||||
|
|
@ -3343,7 +3344,8 @@
|
|||
{
|
||||
"id": 174,
|
||||
"chatUrl": null,
|
||||
"description": "Blockzilla is an optimized and slim list. Light on system resources yet still powerful against ads and trackers.",
|
||||
"description":
|
||||
"Blockzilla is an optimized and slim list. Light on system resources yet still powerful against ads and trackers.",
|
||||
"descriptionSourceUrl": "https://github.com/zpacman/Blockzilla",
|
||||
"discontinuedDate": null,
|
||||
"donateUrl": null,
|
||||
|
|
@ -8257,12 +8259,14 @@
|
|||
"publishedDate": null,
|
||||
"submissionUrl": null,
|
||||
"syntaxId": 1,
|
||||
"viewUrl": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/AdawayHosts"
|
||||
"viewUrl":
|
||||
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianExperimentalList%20alternate%20versions/AdawayHosts"
|
||||
},
|
||||
{
|
||||
"id": 431,
|
||||
"chatUrl": null,
|
||||
"description": "Have you ever felt bothered by how many websites still think that everyone are using narrow monitors? Well, here's how to stretch them out like a piece of gum!",
|
||||
"description":
|
||||
"Have you ever felt bothered by how many websites still think that everyone are using narrow monitors? Well, here's how to stretch them out like a piece of gum!",
|
||||
"descriptionSourceUrl": null,
|
||||
"discontinuedDate": null,
|
||||
"donateUrl": "https://sproutsluckycorner.wordpress.com/2017/11/14/my-work-and-contact-resume/#donations",
|
||||
|
|
@ -8276,6 +8280,7 @@
|
|||
"publishedDate": null,
|
||||
"submissionUrl": null,
|
||||
"syntaxId": 4,
|
||||
"viewUrl": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Dandelion%20Sprout's%20Website%20Stretcher.txt"
|
||||
"viewUrl":
|
||||
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Dandelion%20Sprout's%20Website%20Stretcher.txt"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -671,4 +671,4 @@
|
|||
"filterListID": 430,
|
||||
"languageId": 44
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -719,4 +719,4 @@
|
|||
"filterListId": 430,
|
||||
"maintainerId": 22
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -43,7 +43,8 @@
|
|||
},
|
||||
{
|
||||
"id": 8,
|
||||
"downloadUrl": "https://chrome.google.com/webstore/detail/personal-blocklist-by-goo/nolijncfnkgaikbjbdaogikpmpbdcdef",
|
||||
"downloadUrl":
|
||||
"https://chrome.google.com/webstore/detail/personal-blocklist-by-goo/nolijncfnkgaikbjbdaogikpmpbdcdef",
|
||||
"homeUrl": "https://chrome.google.com/webstore/detail/personal-blocklist-by-goo/nolijncfnkgaikbjbdaogikpmpbdcdef",
|
||||
"name": "Personal Blocklist (Chrome)"
|
||||
},
|
||||
|
|
@ -71,4 +72,4 @@
|
|||
"homeUrl": "https://github.com/2ndalpha/gasmask",
|
||||
"name": "Gas Mask"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -47,4 +47,4 @@
|
|||
"softwareId": 12,
|
||||
"syntaxId": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
@ -25,13 +25,11 @@ public static void Main()
|
|||
CaptureSnapshots(BatchSize);
|
||||
}
|
||||
|
||||
private static void BuildConfigurationRoot()
|
||||
{
|
||||
private static void BuildConfigurationRoot() =>
|
||||
configurationRoot = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", true)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static void InstantiateTelemetryClient()
|
||||
{
|
||||
|
|
@ -42,7 +40,7 @@ private static void InstantiateTelemetryClient()
|
|||
private static void BuildServiceProvider()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddFilterListsServices(configurationRoot);
|
||||
serviceCollection.AddFilterListsAgentServices(configurationRoot);
|
||||
serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,27 +22,20 @@ public static void AddFilterListsApi(this IServiceCollection services)
|
|||
TelemetryDebugWriter.IsTracingDisabled = true;
|
||||
}
|
||||
|
||||
private static void ConfigureCookiePolicy(this IServiceCollection services)
|
||||
{
|
||||
private static void ConfigureCookiePolicy(this IServiceCollection services) =>
|
||||
services.Configure<CookiePolicyOptions>(opts =>
|
||||
{
|
||||
opts.CheckConsentNeeded = context => true;
|
||||
opts.MinimumSameSitePolicy = SameSiteMode.None;
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddMvcCustom(this IServiceCollection services)
|
||||
{
|
||||
private static void AddMvcCustom(this IServiceCollection services) =>
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
}
|
||||
|
||||
private static void AddRoutingCustom(this IServiceCollection services)
|
||||
{
|
||||
private static void AddRoutingCustom(this IServiceCollection services) =>
|
||||
services.AddRouting(opts => opts.LowercaseUrls = true);
|
||||
}
|
||||
|
||||
private static void AddSwaggerGenCustom(this IServiceCollection services)
|
||||
{
|
||||
private static void AddSwaggerGenCustom(this IServiceCollection services) =>
|
||||
services.AddSwaggerGen(opts =>
|
||||
{
|
||||
opts.SwaggerDoc("v1",
|
||||
|
|
@ -54,11 +47,12 @@ private static void AddSwaggerGenCustom(this IServiceCollection services)
|
|||
"A REST-ish API for FilterLists, the independent, comprehensive directory of all public filter and hosts lists for advertisements, trackers, malware, and annoyances." +
|
||||
Environment.NewLine +
|
||||
" - {version} has to be specified manually (to \"1\") in Swagger playground below due to https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/370",
|
||||
Contact = new Contact
|
||||
{
|
||||
Url = "https://github.com/collinbarrett/FilterLists/",
|
||||
Name = "FilterLists - GitHub"
|
||||
},
|
||||
Contact =
|
||||
new Contact
|
||||
{
|
||||
Url = "https://github.com/collinbarrett/FilterLists/",
|
||||
Name = "FilterLists - GitHub"
|
||||
},
|
||||
License = new License
|
||||
{
|
||||
Name = "MIT License",
|
||||
|
|
@ -68,6 +62,5 @@ private static void AddSwaggerGenCustom(this IServiceCollection services)
|
|||
opts.IncludeXmlComments(Path.Combine(PlatformServices.Default.Application.ApplicationBasePath,
|
||||
"FilterLists.Api.xml"));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ namespace FilterLists.Api
|
|||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run();
|
||||
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
|
|
|
|||
|
|
@ -17,17 +17,14 @@ namespace FilterLists.Api
|
|||
[UsedImplicitly]
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
public Startup(IConfiguration configuration) => Configuration = configuration;
|
||||
|
||||
private IConfiguration Configuration { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddFilterListsServices(Configuration);
|
||||
services.AddFilterListsApiServices(Configuration);
|
||||
services.AddFilterListsApi();
|
||||
}
|
||||
|
||||
|
|
@ -72,16 +69,13 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
|||
}
|
||||
|
||||
//TODO: remove hack (https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/74#issuecomment-386762178)
|
||||
private static void UseLowercaseControllerNameInSwaggerHack(SwaggerOptions opts)
|
||||
{
|
||||
private static void UseLowercaseControllerNameInSwaggerHack(SwaggerOptions opts) =>
|
||||
opts.PreSerializeFilters.Add((document, request) =>
|
||||
{
|
||||
var paths = document.Paths.ToDictionary(item => item.Key.ToLowerInvariant(), item => item.Value);
|
||||
document.Paths.Clear();
|
||||
foreach (var pathItem in paths)
|
||||
document.Paths.Add(pathItem.Key, pathItem.Value);
|
||||
foreach (var pathItem in paths) document.Paths.Add(pathItem.Key, pathItem.Value);
|
||||
});
|
||||
}
|
||||
|
||||
private void MigrateAndSeedDatabase(IApplicationBuilder app)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ public BaseController()
|
|||
{
|
||||
}
|
||||
|
||||
protected BaseController(SeedService seedService)
|
||||
{
|
||||
SeedService = seedService;
|
||||
}
|
||||
protected BaseController(SeedService seedService) => SeedService = seedService;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,8 @@ public class ListsController : BaseController
|
|||
{
|
||||
private readonly FilterListService filterListService;
|
||||
|
||||
public ListsController(SeedService seedService, FilterListService filterListService) : base(seedService)
|
||||
{
|
||||
public ListsController(SeedService seedService, FilterListService filterListService) : base(seedService) =>
|
||||
this.filterListService = filterListService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index() => Json(await filterListService.GetAllSummariesAsync());
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ public ListsMaintainersController(SeedService seedService) : base(seedService)
|
|||
}
|
||||
|
||||
[HttpGet("seed")]
|
||||
public async Task<IActionResult> Seed() => Json(
|
||||
await SeedService.GetAllAsync<FilterListMaintainer, FilterListMaintainerSeedDto>(
|
||||
public async Task<IActionResult> Seed() =>
|
||||
Json(await SeedService.GetAllAsync<FilterListMaintainer, FilterListMaintainerSeedDto>(
|
||||
typeof(FilterListMaintainer).GetProperty("MaintainerId"),
|
||||
typeof(FilterListMaintainer).GetProperty("FilterListId")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ public class RulesController : BaseController
|
|||
{
|
||||
private readonly RuleService ruleService;
|
||||
|
||||
public RulesController(RuleService ruleService)
|
||||
{
|
||||
this.ruleService = ruleService;
|
||||
}
|
||||
public RulesController(RuleService ruleService) => this.ruleService = ruleService;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index() => Json(await ruleService.GetCountAll());
|
||||
|
|
|
|||
|
|
@ -7,11 +7,7 @@ namespace FilterLists.Data.EntityTypeConfigurations.Junctions
|
|||
public class BaseJunctionTypeConfiguration<TJunction> : IEntityTypeConfiguration<TJunction>
|
||||
where TJunction : BaseJunctionEntity
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<TJunction> entityTypeBuilder)
|
||||
{
|
||||
entityTypeBuilder.Property(x => x.CreatedDateUtc)
|
||||
.HasColumnType("TIMESTAMP")
|
||||
.ValueGeneratedOnAdd();
|
||||
}
|
||||
public virtual void Configure(EntityTypeBuilder<TJunction> entityTypeBuilder) =>
|
||||
entityTypeBuilder.Property(x => x.CreatedDateUtc).HasColumnType("TIMESTAMP").ValueGeneratedOnAdd();
|
||||
}
|
||||
}
|
||||
|
|
@ -42,21 +42,17 @@ private static void InsertOnDuplicateKeyUpdate<TEntity>(this DbContext dbContext
|
|||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
private static List<IProperty> GetPropertiesLessValueGeneratedTimestamps(IEntityType entityType)
|
||||
{
|
||||
//TODO: get seed properties dynamically from JSON
|
||||
return entityType.GetProperties()
|
||||
.Where(x => !new List<string> {"CreatedDateUtc", "ModifiedDateUtc"}.Contains(x.Name))
|
||||
.ToList();
|
||||
}
|
||||
//TODO: get seed properties dynamically from JSON
|
||||
private static List<IProperty> GetPropertiesLessValueGeneratedTimestamps(IEntityType entityType) =>
|
||||
entityType.GetProperties()
|
||||
.Where(x => !new List<string> {"CreatedDateUtc", "ModifiedDateUtc"}.Contains(x.Name))
|
||||
.ToList();
|
||||
|
||||
private static string CreateValues<TEntity>(IReadOnlyCollection<IProperty> properties, string dataPath)
|
||||
where TEntity : IBaseEntity
|
||||
{
|
||||
return GetSeedRows<TEntity>(dataPath)
|
||||
.Select(row => CreateRowValues(properties, row))
|
||||
.Aggregate("", (current, rowValues) => current == "" ? rowValues : current + ", " + rowValues);
|
||||
}
|
||||
where TEntity : IBaseEntity =>
|
||||
GetSeedRows<TEntity>(dataPath)
|
||||
.Select(row => CreateRowValues(properties, row))
|
||||
.Aggregate("", (current, rowValues) => current == "" ? rowValues : current + ", " + rowValues);
|
||||
|
||||
private static List<TEntity> GetSeedRows<TEntity>(string dataPath) where TEntity : IBaseEntity
|
||||
{
|
||||
|
|
@ -73,13 +69,11 @@ private static List<TEntity> GetSeedRows<TEntity>(string dataPath) where TEntity
|
|||
}
|
||||
|
||||
private static string CreateRowValues<TEntity>(IEnumerable<IProperty> properties, TEntity row)
|
||||
where TEntity : IBaseEntity
|
||||
{
|
||||
return (from property in properties
|
||||
let value = row.GetType().GetProperty(property.Name)?.GetValue(row)
|
||||
select FormatDataForMySql(property, value)).Aggregate("",
|
||||
(rowValues, value) => rowValues == "" ? "(" + value : rowValues + ", " + value) + ")";
|
||||
}
|
||||
where TEntity : IBaseEntity =>
|
||||
(from property in properties
|
||||
let value = row.GetType().GetProperty(property.Name)?.GetValue(row)
|
||||
select FormatDataForMySql(property, value)).Aggregate("",
|
||||
(rowValues, value) => rowValues == "" ? "(" + value : rowValues + ", " + value) + ")";
|
||||
|
||||
private static object FormatDataForMySql(IProperty property, object value)
|
||||
{
|
||||
|
|
@ -89,7 +83,7 @@ private static object FormatDataForMySql(IProperty property, object value)
|
|||
if (property.ClrType == typeof(bool))
|
||||
return Convert.ToInt32(value);
|
||||
if (property.ClrType == typeof(DateTime?))
|
||||
return "'" + ((DateTime) value).ToString("yyyy-MM-dd HH:mm:ss") + "'";
|
||||
return "'" + ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss") + "'";
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +94,8 @@ private static string CreateUpdates(IReadOnlyCollection<IProperty> properties)
|
|||
where !property.IsPrimaryKey()
|
||||
select property.Name + " = VALUES(" + property.Name + ")").Aggregate("",
|
||||
(updates, columnUpdates) => updates == "" ? columnUpdates : updates + ", " + columnUpdates);
|
||||
if (update == "") update = GetUpdateUnchangedColumnHack(properties);
|
||||
if (update == "")
|
||||
update = GetUpdateUnchangedColumnHack(properties);
|
||||
return update;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,21 @@ namespace FilterLists.Services.DependencyInjection.Extensions
|
|||
{
|
||||
public static class ConfigureServicesCollection
|
||||
{
|
||||
public static void AddFilterListsServices(this IServiceCollection services, IConfiguration config)
|
||||
public static void AddFilterListsApiServices(this IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddSingleton(c => config);
|
||||
services.AddEntityFrameworkMySql()
|
||||
.AddDbContextPool<FilterListsDbContext>(opts =>
|
||||
opts.UseMySql(config.GetConnectionString("FilterListsConnection"),
|
||||
x => x.MigrationsAssembly("FilterLists.Api"))
|
||||
.EnableSensitiveDataLogging());
|
||||
services.TryAddScoped<FilterListService>();
|
||||
services.TryAddScoped<RuleService>();
|
||||
services.TryAddScoped<SeedService>();
|
||||
services.AddAutoMapper();
|
||||
}
|
||||
|
||||
public static void AddFilterListsAgentServices(this IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddSingleton(c => config);
|
||||
services.AddEntityFrameworkMySql()
|
||||
|
|
@ -21,9 +35,6 @@ public static void AddFilterListsServices(this IServiceCollection services, ICon
|
|||
x => x.MigrationsAssembly("FilterLists.Api"))
|
||||
.EnableSensitiveDataLogging());
|
||||
services.TryAddScoped<SnapshotService>();
|
||||
services.TryAddScoped<FilterListService>();
|
||||
services.TryAddScoped<RuleService>();
|
||||
services.TryAddScoped<SeedService>();
|
||||
services.AddAutoMapper();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using FilterLists.Data;
|
||||
using FilterLists.Services.FilterList.Models;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
|
@ -43,15 +44,13 @@ await DbContext.FilterLists.OrderBy(l => l.Name)
|
|||
.ProjectTo<ListSummaryDto>(Mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
private async Task<List<Data.Entities.Snapshot>> GetLatestSnapshots()
|
||||
{
|
||||
return await DbContext.Snapshots.AsNoTracking()
|
||||
.Where(s => s.IsCompleted && s.HttpStatusCode == "200" &&
|
||||
(s.AddedSnapshotRules.Count > 0 || s.RemovedSnapshotRules.Count > 0))
|
||||
.GroupBy(s => s.FilterListId,
|
||||
(key, x) => x.OrderByDescending(y => y.CreatedDateUtc).First())
|
||||
.ToListAsync();
|
||||
}
|
||||
private async Task<List<Data.Entities.Snapshot>> GetLatestSnapshots() =>
|
||||
await DbContext.Snapshots.AsNoTracking()
|
||||
.Where(s => s.IsCompleted && s.HttpStatusCode == "200" &&
|
||||
(s.AddedSnapshotRules.Count > 0 || s.RemovedSnapshotRules.Count > 0))
|
||||
.GroupBy(s => s.FilterListId,
|
||||
(key, x) => x.OrderByDescending(y => y.CreatedDateUtc).First())
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<ListDetailsDto> GetDetailsAsync(uint id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FilterLists.Services.FilterList.Models;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using FilterLists.Data.Entities;
|
||||
using FilterLists.Services.FilterList.Models;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListDetailsDto
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListLanguagesDto
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListLicenseDto
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListMaintainerDto
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListSummaryDto
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ListSyntaxDto
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class MaintainerAdditionalListsDto
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.FilterList
|
||||
namespace FilterLists.Services.FilterList.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class SyntaxSupportedSoftwareDto
|
||||
|
|
@ -12,8 +12,7 @@ public RuleService(FilterListsDbContext dbContext) : base(dbContext)
|
|||
{
|
||||
}
|
||||
|
||||
public async Task<int> GetCountAll() => await DbContext
|
||||
.Rules.AsNoTracking()
|
||||
.CountAsync();
|
||||
public async Task<int> GetCountAll() =>
|
||||
await DbContext.Rules.AsNoTracking().CountAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -21,17 +21,18 @@ public async Task<IEnumerable<TSeedDto>> GetAllAsync<TEntity, TSeedDto>() where
|
|||
await DbContext.Set<TEntity>().ProjectTo<TSeedDto>(Mapper.ConfigurationProvider).ToArrayAsync();
|
||||
|
||||
public async Task<IEnumerable<TSeedDto>> GetAllAsync<TEntity, TSeedDto>(PropertyInfo primarySort)
|
||||
where TEntity : class => await DbContext.Set<TEntity>()
|
||||
.OrderBy(x => primarySort.GetValue(x, null))
|
||||
.ProjectTo<TSeedDto>(Mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
where TEntity : class =>
|
||||
await DbContext.Set<TEntity>()
|
||||
.OrderBy(x => primarySort.GetValue(x, null))
|
||||
.ProjectTo<TSeedDto>(Mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
|
||||
public async Task<IEnumerable<TSeedDto>>
|
||||
GetAllAsync<TEntity, TSeedDto>(PropertyInfo primarySort, PropertyInfo secondarySort)
|
||||
where TEntity : class => await DbContext.Set<TEntity>()
|
||||
.OrderBy(x => primarySort.GetValue(x, null))
|
||||
.ThenBy(x => secondarySort.GetValue(x, null))
|
||||
.ProjectTo<TSeedDto>(Mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
public async Task<IEnumerable<TSeedDto>> GetAllAsync<TEntity, TSeedDto>(PropertyInfo primarySort,
|
||||
PropertyInfo secondarySort) where TEntity : class =>
|
||||
await DbContext.Set<TEntity>()
|
||||
.OrderBy(x => primarySort.GetValue(x, null))
|
||||
.ThenBy(x => secondarySort.GetValue(x, null))
|
||||
.ProjectTo<TSeedDto>(Mapper.ConfigurationProvider)
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,10 +10,7 @@ public class Service
|
|||
protected readonly FilterListsDbContext DbContext;
|
||||
protected readonly IMapper Mapper;
|
||||
|
||||
public Service(FilterListsDbContext dbContext)
|
||||
{
|
||||
DbContext = dbContext;
|
||||
}
|
||||
public Service(FilterListsDbContext dbContext) => DbContext = dbContext;
|
||||
|
||||
public Service(FilterListsDbContext dbContext, IMapper mapper)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using JetBrains.Annotations;
|
||||
|
||||
namespace FilterLists.Services.Snapshot
|
||||
namespace FilterLists.Services.Snapshot.Models
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class FilterListViewUrlDto
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
using FilterLists.Data;
|
||||
using FilterLists.Data.Entities.Junctions;
|
||||
using FilterLists.Services.Extensions;
|
||||
using FilterLists.Services.Snapshot.Models;
|
||||
|
||||
namespace FilterLists.Services.Snapshot
|
||||
{
|
||||
|
|
@ -60,7 +61,7 @@ private async Task<string> TryGetContent()
|
|||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
snapshot.HttpStatusCode = ((int) ((HttpWebResponse) we.Response).StatusCode).ToString();
|
||||
snapshot.HttpStatusCode = ((int)((HttpWebResponse)we.Response).StatusCode).ToString();
|
||||
return null;
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
@ -78,7 +79,7 @@ private async Task<string> GetContent()
|
|||
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgentString);
|
||||
using (var httpResponseMessage = await httpClient.GetAsync(list.ViewUrl))
|
||||
{
|
||||
snapshot.HttpStatusCode = ((int) httpResponseMessage.StatusCode).ToString();
|
||||
snapshot.HttpStatusCode = ((int)httpResponseMessage.StatusCode).ToString();
|
||||
if (httpResponseMessage.IsSuccessStatusCode)
|
||||
return await httpResponseMessage.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
|
@ -102,11 +103,9 @@ private static IEnumerable<string> GetRawRules(string content)
|
|||
return new HashSet<string>(rawRules.Where(rr => rr != null));
|
||||
}
|
||||
|
||||
private IEnumerable<SnapshotBatchDe> GetSnapshotBatches(IEnumerable<string> rawRules)
|
||||
{
|
||||
return rawRules.GetBatches(BatchSize)
|
||||
.Select(rawRuleBatch => new SnapshotBatchDe(dbContext, snapshot, rawRuleBatch));
|
||||
}
|
||||
private IEnumerable<SnapshotBatchDe> GetSnapshotBatches(IEnumerable<string> rawRules) =>
|
||||
rawRules.GetBatches(BatchSize)
|
||||
.Select(rawRuleBatch => new SnapshotBatchDe(dbContext, snapshot, rawRuleBatch));
|
||||
|
||||
private static async Task SaveSnapshotBatches(IEnumerable<SnapshotBatchDe> snapshotBatches)
|
||||
{
|
||||
|
|
@ -122,12 +121,10 @@ private async Task DedupSnapshotRules()
|
|||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private IQueryable<SnapshotRule> GetExistingSnapshotRules()
|
||||
{
|
||||
return dbContext.SnapshotRules.Where(sr =>
|
||||
private IQueryable<SnapshotRule> GetExistingSnapshotRules() =>
|
||||
dbContext.SnapshotRules.Where(sr =>
|
||||
sr.AddedBySnapshot.FilterListId == list.Id && sr.AddedBySnapshot != snapshot &&
|
||||
sr.RemovedBySnapshot == null);
|
||||
}
|
||||
|
||||
private void UpdateRemovedSnapshotRules(IQueryable<SnapshotRule> existingSnapshotRules)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using FilterLists.Data;
|
||||
using FilterLists.Services.Snapshot.Models;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
|
@ -14,7 +16,8 @@ namespace FilterLists.Services.Snapshot
|
|||
public class SnapshotService : Service
|
||||
{
|
||||
//TODO: update algorithm to support non-standard list sizes and formats (#200, #201)
|
||||
private readonly List<uint> ignoreLists = new List<uint> {48, 149, 173, 185, 186, 187, 188, 189, 352};
|
||||
private static readonly IList<uint> IgnoreLists =
|
||||
new ReadOnlyCollection<uint>(new List<uint> {48, 149, 173, 185, 186, 187, 188, 189, 352});
|
||||
|
||||
public SnapshotService(FilterListsDbContext dbContext, IMapper mapper) : base(dbContext, mapper)
|
||||
{
|
||||
|
|
@ -41,7 +44,7 @@ await DbContext
|
|||
.Where(list =>
|
||||
(!list.Snapshots.Any() ||
|
||||
list.Snapshots.Select(ss => ss.CreatedDateUtc).OrderByDescending(sscd => sscd).FirstOrDefault() <
|
||||
DateTime.UtcNow.AddDays(-1)) && !ignoreLists.Contains(list.Id))
|
||||
DateTime.UtcNow.AddDays(-1)) && !IgnoreLists.Contains(list.Id))
|
||||
.OrderBy(list => list.Snapshots.Any())
|
||||
.ThenBy(list =>
|
||||
list.Snapshots.Select(ss => ss.CreatedDateUtc).OrderByDescending(sscd => sscd).FirstOrDefault())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import "bootstrap";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { AppContainer } from "react-hot-loader";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import * as RoutesModule from "./routes";
|
||||
import "bootstrap";
|
||||
import "../css/site.css";
|
||||
let routes = RoutesModule.routes;
|
||||
|
||||
function renderApp() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { RouteComponentProps } from "react-router";
|
|||
import "isomorphic-fetch";
|
||||
import ReactTable from "react-table"
|
||||
import "react-table/react-table.css"
|
||||
import "../css/site.css";
|
||||
import ListDetails from "./ListDetails";
|
||||
import * as moment from "moment";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,7 @@ namespace FilterLists.Web.Controllers
|
|||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
public IActionResult Index() => View();
|
||||
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,18 +13,14 @@ public static void AddFilterListsWeb(this IServiceCollection services)
|
|||
services.AddMvcCustom();
|
||||
}
|
||||
|
||||
private static void ConfigureCookiePolicy(this IServiceCollection services)
|
||||
{
|
||||
private static void ConfigureCookiePolicy(this IServiceCollection services) =>
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
options.CheckConsentNeeded = context => true;
|
||||
options.MinimumSameSitePolicy = SameSiteMode.None;
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddMvcCustom(this IServiceCollection services)
|
||||
{
|
||||
private static void AddMvcCustom(this IServiceCollection services) =>
|
||||
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,14 +5,9 @@ namespace FilterLists.Web
|
|||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
public static void Main(string[] args) => CreateWebHostBuilder(args).Build().Run();
|
||||
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost
|
||||
.CreateDefaultBuilder(args)
|
||||
.UseUrls("http://localhost:5001;")
|
||||
.UseStartup<Startup>();
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args).UseUrls("http://localhost:5001;").UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,10 @@ namespace FilterLists.Web
|
|||
[UsedImplicitly]
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddFilterListsWeb();
|
||||
}
|
||||
[UsedImplicitly]
|
||||
public void ConfigureServices(IServiceCollection services) => services.AddFilterListsWeb();
|
||||
|
||||
[UsedImplicitly]
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
ViewData["Title"] = "Subscriptions for uBlock Origin, Adblock Plus, AdGuard, ...";
|
||||
}
|
||||
<div id="react-app">Loading...</div>
|
||||
|
||||
@section scripts {
|
||||
<script src="~/dist/main.js" asp-append-version="true"></script>
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (!string.IsNullOrEmpty((string)ViewData["RequestId"]))
|
||||
@if (!string.IsNullOrEmpty((string) ViewData["RequestId"]))
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@ViewData["RequestId"]</code>
|
||||
|
|
|
|||
32
src/FilterLists.Web/npm-shrinkwrap.json
generated
32
src/FilterLists.Web/npm-shrinkwrap.json
generated
|
|
@ -1712,7 +1712,8 @@
|
|||
"is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"integrity":
|
||||
"sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-object": "2.0.4"
|
||||
|
|
@ -3704,7 +3705,8 @@
|
|||
"is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"integrity":
|
||||
"sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-object": "2.0.4"
|
||||
|
|
@ -3809,7 +3811,8 @@
|
|||
"process-nextick-args": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
|
||||
"integrity":
|
||||
"sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
|
||||
"dev": true
|
||||
},
|
||||
"readable-stream": {
|
||||
|
|
@ -4376,7 +4379,8 @@
|
|||
},
|
||||
"postcss-modules-extract-imports": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz",
|
||||
"resolved":
|
||||
"https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz",
|
||||
"integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
@ -4439,7 +4443,8 @@
|
|||
},
|
||||
"postcss-modules-local-by-default": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
|
||||
"resolved":
|
||||
"https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz",
|
||||
"integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
|
@ -4931,7 +4936,8 @@
|
|||
"prop-types": {
|
||||
"version": "15.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
|
||||
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
||||
"integrity":
|
||||
"sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "1.3.1",
|
||||
|
|
@ -5762,7 +5768,8 @@
|
|||
"ajv": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz",
|
||||
"integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==",
|
||||
"integrity":
|
||||
"sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "2.0.1",
|
||||
|
|
@ -5780,13 +5787,15 @@
|
|||
"json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"integrity":
|
||||
"sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
|
||||
"integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
|
||||
"integrity":
|
||||
"sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "6.5.2",
|
||||
|
|
@ -6060,7 +6069,8 @@
|
|||
"punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"integrity":
|
||||
"sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
|
@ -6586,4 +6596,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,4 +37,4 @@
|
|||
"popper.js": "^1.14.4",
|
||||
"react-table": "^6.8.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue