mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
refactor(svcs): ♻🔥 use file scoped namespaces
This commit is contained in:
parent
6c9dc865b5
commit
1649ac6778
80 changed files with 1777 additions and 1853 deletions
|
|
@ -138,4 +138,7 @@ visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public
|
|||
dotnet_diagnostic.CS1591.severity = silent
|
||||
|
||||
#https://github.com/JosefPihrt/Roslynator/blob/master/docs/Options.md
|
||||
roslynator.RCS1090.invert = true
|
||||
roslynator.RCS1090.invert = true
|
||||
|
||||
#https://github.com/SonarSource/sonar-dotnet/issues/4731
|
||||
dotnet_diagnostic.S3903.severity = silent
|
||||
|
|
@ -1,29 +1,28 @@
|
|||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FilterLists.Archival.Api.Controllers
|
||||
namespace FilterLists.Archival.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
// TODO: de-duplicate into SharedKernel
|
||||
public class ErrorController : ControllerBase
|
||||
{
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
// TODO: de-duplicate into SharedKernel
|
||||
public class ErrorController : ControllerBase
|
||||
[Route("/error-local-development")]
|
||||
public IActionResult ErrorLocalDevelopment([FromServices] IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
[Route("/error-local-development")]
|
||||
public IActionResult ErrorLocalDevelopment([FromServices] IWebHostEnvironment webHostEnvironment)
|
||||
if (webHostEnvironment.EnvironmentName != "Development")
|
||||
{
|
||||
if (webHostEnvironment.EnvironmentName != "Development")
|
||||
{
|
||||
throw new InvalidOperationException("This shouldn't be invoked in non-development environments.");
|
||||
}
|
||||
|
||||
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
return Problem(context?.Error.StackTrace, title: context?.Error.Message);
|
||||
throw new InvalidOperationException("This shouldn't be invoked in non-development environments.");
|
||||
}
|
||||
|
||||
[Route("/error")]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return Problem();
|
||||
}
|
||||
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
return Problem(context?.Error.StackTrace, title: context?.Error.Message);
|
||||
}
|
||||
|
||||
[Route("/error")]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return Problem();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FilterLists.Archival.Api.Controllers
|
||||
namespace FilterLists.Archival.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class PingController : ControllerBase
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class PingController : ControllerBase
|
||||
/// <summary>
|
||||
/// A sample endpoint.
|
||||
/// </summary>
|
||||
/// <returns>Pong.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||
public ActionResult<string> Ping()
|
||||
{
|
||||
/// <summary>
|
||||
/// A sample endpoint.
|
||||
/// </summary>
|
||||
/// <returns>Pong.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
|
||||
public ActionResult<string> Ping()
|
||||
{
|
||||
return "pong";
|
||||
}
|
||||
return "pong";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
using FilterLists.Archival.Application;
|
||||
using FilterLists.SharedKernel.Logging;
|
||||
|
||||
namespace FilterLists.Archival.Api
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// TODO: migrate to new hosting model https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio#new-hosting-model
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
await host.TryRunWithLoggingAsync();
|
||||
}
|
||||
namespace FilterLists.Archival.Api;
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
{
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseApplication()
|
||||
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
|
||||
}
|
||||
public static class Program
|
||||
{
|
||||
// TODO: migrate to new hosting model https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio#new-hosting-model
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
await host.TryRunWithLoggingAsync();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
{
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseApplication()
|
||||
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using FilterLists.Archival.Application;
|
||||
|
||||
namespace FilterLists.Archival.Api
|
||||
namespace FilterLists.Archival.Api;
|
||||
|
||||
internal class Startup
|
||||
{
|
||||
internal class Startup
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting(o => o.LowercaseUrls = true);
|
||||
services.AddControllers().AddJsonOptions(o =>
|
||||
o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull);
|
||||
services.AddSwaggerGen();
|
||||
services.AddApplicationServices(Configuration);
|
||||
}
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddRouting(o => o.LowercaseUrls = true);
|
||||
services.AddControllers().AddJsonOptions(o =>
|
||||
o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull);
|
||||
services.AddSwaggerGen();
|
||||
services.AddApplicationServices(Configuration);
|
||||
}
|
||||
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseApplication();
|
||||
app.UseExceptionHandler(env.IsDevelopment() ? "/error-local-development" : "/error");
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(e => e.MapControllers());
|
||||
app.UseSwagger();
|
||||
}
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseApplication();
|
||||
app.UseExceptionHandler(env.IsDevelopment() ? "/error-local-development" : "/error");
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(e => e.MapControllers());
|
||||
app.UseSwagger();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,44 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace FilterLists.Archival.Api
|
||||
namespace FilterLists.Archival.Api;
|
||||
|
||||
internal static class SwaggerExtensions
|
||||
{
|
||||
internal static class SwaggerExtensions
|
||||
public static void AddSwaggerGen(this IServiceCollection services)
|
||||
{
|
||||
public static void AddSwaggerGen(this IServiceCollection services)
|
||||
services.AddSwaggerGen(o =>
|
||||
{
|
||||
services.AddSwaggerGen(o =>
|
||||
o.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
o.SwaggerDoc("v1", new OpenApiInfo
|
||||
Title = "FilterLists Archival API",
|
||||
Description =
|
||||
"An ASP.NET Core API archiving and serving copies of FilterLists for mirrors and analysis.",
|
||||
Version = "v1",
|
||||
//TermsOfService = "",
|
||||
Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") },
|
||||
License = new OpenApiLicense
|
||||
{
|
||||
Title = "FilterLists Archival API",
|
||||
Description =
|
||||
"An ASP.NET Core API archiving and serving copies of FilterLists for mirrors and analysis.",
|
||||
Version = "v1",
|
||||
//TermsOfService = "",
|
||||
Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") },
|
||||
License = new OpenApiLicense
|
||||
{
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/master/LICENSE")
|
||||
}
|
||||
});
|
||||
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
o.IncludeXmlComments(xmlPath);
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/master/LICENSE")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseSwagger(this IApplicationBuilder app)
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
o.IncludeXmlComments(xmlPath);
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseSwagger(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseSwagger(o =>
|
||||
{
|
||||
app.UseSwagger(o =>
|
||||
o.RouteTemplate = "{documentName}/swagger.json";
|
||||
o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Servers = new List<OpenApiServer>
|
||||
{
|
||||
o.RouteTemplate = "{documentName}/swagger.json";
|
||||
o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Servers = new List<OpenApiServer>
|
||||
{
|
||||
new() { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/api/archival" }
|
||||
});
|
||||
new() { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/api/archival" }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,93 +6,92 @@
|
|||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FilterLists.Archival.Application.Commands
|
||||
{
|
||||
public static class ArchiveList
|
||||
{
|
||||
public class Command : IRequest
|
||||
{
|
||||
public Command(int listId)
|
||||
{
|
||||
ListId = listId;
|
||||
}
|
||||
namespace FilterLists.Archival.Application.Commands;
|
||||
|
||||
public int ListId { get; }
|
||||
public static class ArchiveList
|
||||
{
|
||||
public class Command : IRequest
|
||||
{
|
||||
public Command(int listId)
|
||||
{
|
||||
ListId = listId;
|
||||
}
|
||||
|
||||
public class Handler : IRequestHandler<Command, Unit>
|
||||
public int ListId { get; }
|
||||
}
|
||||
|
||||
public class Handler : IRequestHandler<Command, Unit>
|
||||
{
|
||||
private readonly IHttpContentClient _client;
|
||||
private readonly IDirectoryApi _directory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IListArchiveRepository _repo;
|
||||
|
||||
public Handler(
|
||||
IHttpContentClient httpContentClient,
|
||||
IDirectoryApi directoryApi,
|
||||
ILogger<Handler> logger,
|
||||
IListArchiveRepository listArchiveRepository)
|
||||
{
|
||||
private readonly IHttpContentClient _client;
|
||||
private readonly IDirectoryApi _directory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IListArchiveRepository _repo;
|
||||
_client = httpContentClient;
|
||||
_directory = directoryApi;
|
||||
_logger = logger;
|
||||
_repo = listArchiveRepository;
|
||||
}
|
||||
|
||||
public Handler(
|
||||
IHttpContentClient httpContentClient,
|
||||
IDirectoryApi directoryApi,
|
||||
ILogger<Handler> logger,
|
||||
IListArchiveRepository listArchiveRepository)
|
||||
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Archiving list {ListId}", request.ListId);
|
||||
|
||||
var segmentUrls = (await GetSegmentUrlsAsync(request.ListId, cancellationToken)).ToList();
|
||||
if (segmentUrls.Count > 0)
|
||||
{
|
||||
_client = httpContentClient;
|
||||
_directory = directoryApi;
|
||||
_logger = logger;
|
||||
_repo = listArchiveRepository;
|
||||
var list = GetList(request.ListId, segmentUrls, cancellationToken);
|
||||
await _repo.AddAsync(list, cancellationToken);
|
||||
_repo.Commit();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Archived segment(s) {@SegmentNumbers} of list {ListId}",
|
||||
segmentUrls.Select(s => s.SegmentNumber),
|
||||
request.ListId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("List {ListId} has no URLs to archive", request.ListId);
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Archiving list {ListId}", request.ListId);
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
var segmentUrls = (await GetSegmentUrlsAsync(request.ListId, cancellationToken)).ToList();
|
||||
if (segmentUrls.Count > 0)
|
||||
private async Task<IEnumerable<ListDetailsVm.ViewUrlVm>> GetSegmentUrlsAsync(
|
||||
int listId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var listDetails = await _directory.GetListDetailsAsync(listId, cancellationToken);
|
||||
return listDetails.ViewUrls?
|
||||
.GroupBy(u => u.SegmentNumber, (_, ue) => ue.OrderBy(u => u.Primariness).First()) ??
|
||||
new List<ListDetailsVm.ViewUrlVm>();
|
||||
}
|
||||
|
||||
private ListArchive GetList(
|
||||
int listId,
|
||||
IEnumerable<ListDetailsVm.ViewUrlVm> segmentUrls,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var segmentsAsync = GetSegmentsAsync(segmentUrls, cancellationToken);
|
||||
return new ListArchive(listId, segmentsAsync);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ListArchiveSegment> GetSegmentsAsync(
|
||||
IEnumerable<ListDetailsVm.ViewUrlVm> segmentUrls,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var segment in segmentUrls)
|
||||
{
|
||||
var content = await _client.GetContentAsync(segment.Url, cancellationToken);
|
||||
if (content != Stream.Null)
|
||||
{
|
||||
var list = GetList(request.ListId, segmentUrls, cancellationToken);
|
||||
await _repo.AddAsync(list, cancellationToken);
|
||||
_repo.Commit();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Archived segment(s) {@SegmentNumbers} of list {ListId}",
|
||||
segmentUrls.Select(s => s.SegmentNumber),
|
||||
request.ListId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("List {ListId} has no URLs to archive", request.ListId);
|
||||
}
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<ListDetailsVm.ViewUrlVm>> GetSegmentUrlsAsync(
|
||||
int listId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var listDetails = await _directory.GetListDetailsAsync(listId, cancellationToken);
|
||||
return listDetails.ViewUrls?
|
||||
.GroupBy(u => u.SegmentNumber, (_, ue) => ue.OrderBy(u => u.Primariness).First()) ??
|
||||
new List<ListDetailsVm.ViewUrlVm>();
|
||||
}
|
||||
|
||||
private ListArchive GetList(
|
||||
int listId,
|
||||
IEnumerable<ListDetailsVm.ViewUrlVm> segmentUrls,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var segmentsAsync = GetSegmentsAsync(segmentUrls, cancellationToken);
|
||||
return new ListArchive(listId, segmentsAsync);
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<ListArchiveSegment> GetSegmentsAsync(
|
||||
IEnumerable<ListDetailsVm.ViewUrlVm> segmentUrls,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var segment in segmentUrls)
|
||||
{
|
||||
var content = await _client.GetContentAsync(segment.Url, cancellationToken);
|
||||
if (content != Stream.Null)
|
||||
{
|
||||
yield return new ListArchiveSegment(segment.Url, content);
|
||||
}
|
||||
yield return new ListArchiveSegment(segment.Url, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,51 +3,50 @@
|
|||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FilterLists.Archival.Application.Commands
|
||||
namespace FilterLists.Archival.Application.Commands;
|
||||
|
||||
public static class EnqueueArchiveAllLists
|
||||
{
|
||||
public static class EnqueueArchiveAllLists
|
||||
public class Command : IRequest
|
||||
{
|
||||
public class Command : IRequest
|
||||
}
|
||||
|
||||
public class Handler : IRequestHandler<Command, Unit>
|
||||
{
|
||||
private readonly IDirectoryApi _directory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public Handler(IDirectoryApi directory, ILogger<Handler> logger)
|
||||
{
|
||||
_directory = directory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public class Handler : IRequestHandler<Command, Unit>
|
||||
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IDirectoryApi _directory;
|
||||
private readonly ILogger _logger;
|
||||
var r = new Random();
|
||||
var lists = (await _directory.GetListsAsync(cancellationToken)).OrderBy(_ => r.Next()).ToList();
|
||||
|
||||
public Handler(IDirectoryApi directory, ILogger<Handler> logger)
|
||||
{
|
||||
_directory = directory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
|
||||
{
|
||||
var r = new Random();
|
||||
var lists = (await _directory.GetListsAsync(cancellationToken)).OrderBy(_ => r.Next()).ToList();
|
||||
|
||||
int archiveCount;
|
||||
TimeSpan spacing;
|
||||
int archiveCount;
|
||||
TimeSpan spacing;
|
||||
#if DEBUG
|
||||
archiveCount = 0;
|
||||
spacing = TimeSpan.FromSeconds(5);
|
||||
archiveCount = 0;
|
||||
spacing = TimeSpan.FromSeconds(5);
|
||||
#else
|
||||
archiveCount = 0;
|
||||
//archiveCount = lists.Count;
|
||||
spacing = TimeSpan.FromSeconds((double)86400 / lists.Count);
|
||||
#endif
|
||||
|
||||
_logger.LogInformation("Enqueuing archival of {ArchiveCount} lists spaced {Spacing} seconds apart.",
|
||||
archiveCount, spacing.Seconds);
|
||||
_logger.LogInformation("Enqueuing archival of {ArchiveCount} lists spaced {Spacing} seconds apart.",
|
||||
archiveCount, spacing.Seconds);
|
||||
|
||||
for (var i = 0; i < archiveCount; i++)
|
||||
{
|
||||
new ArchiveList.Command(lists[i].Id).ScheduleBackgroundJob(i * spacing);
|
||||
}
|
||||
|
||||
return Unit.Value;
|
||||
for (var i = 0; i < archiveCount; i++)
|
||||
{
|
||||
new ArchiveList.Command(lists[i].Id).ScheduleBackgroundJob(i * spacing);
|
||||
}
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,35 +8,34 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Archival.Application
|
||||
namespace FilterLists.Archival.Application;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static IHostBuilder UseApplication(this IHostBuilder hostBuilder)
|
||||
{
|
||||
public static IHostBuilder UseApplication(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.UseInfrastructure();
|
||||
}
|
||||
return hostBuilder.UseInfrastructure();
|
||||
}
|
||||
|
||||
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddInfrastructureServices(configuration);
|
||||
}
|
||||
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddInfrastructureServices(configuration);
|
||||
}
|
||||
|
||||
public static void UseApplication(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseInfrastructure();
|
||||
ScheduleArchival();
|
||||
}
|
||||
public static void UseApplication(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseInfrastructure();
|
||||
ScheduleArchival();
|
||||
}
|
||||
|
||||
private static void ScheduleArchival()
|
||||
{
|
||||
private static void ScheduleArchival()
|
||||
{
|
||||
#if DEBUG
|
||||
JobStorage.Current?.GetMonitoringApi()?.PurgeJobs();
|
||||
new EnqueueArchiveAllLists.Command().EnqueueBackgroundJob();
|
||||
JobStorage.Current?.GetMonitoringApi()?.PurgeJobs();
|
||||
new EnqueueArchiveAllLists.Command().EnqueueBackgroundJob();
|
||||
#else
|
||||
new EnqueueArchiveAllLists.Command().AddOrUpdateRecurringJob(Cron.Daily);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
namespace FilterLists.Archival.Domain.Extensions
|
||||
namespace FilterLists.Archival.Domain.Extensions;
|
||||
|
||||
internal static class UriExtension
|
||||
{
|
||||
internal static class UriExtension
|
||||
private static readonly Uri DummyBaseUri = new("http://localhost");
|
||||
|
||||
public static string GetFileExtension(this Uri uri)
|
||||
{
|
||||
private static readonly Uri DummyBaseUri = new("http://localhost");
|
||||
|
||||
public static string GetFileExtension(this Uri uri)
|
||||
if (!uri.IsAbsoluteUri)
|
||||
{
|
||||
if (!uri.IsAbsoluteUri)
|
||||
{
|
||||
uri = new Uri(DummyBaseUri, uri);
|
||||
}
|
||||
|
||||
return Path.GetExtension(uri.LocalPath);
|
||||
uri = new Uri(DummyBaseUri, uri);
|
||||
}
|
||||
|
||||
return Path.GetExtension(uri.LocalPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using FilterLists.Archival.Domain.SeedWork;
|
||||
|
||||
namespace FilterLists.Archival.Domain.ListArchives
|
||||
namespace FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
public interface IListArchiveRepository : IUnitOfWork
|
||||
{
|
||||
public interface IListArchiveRepository : IUnitOfWork
|
||||
{
|
||||
Task AddAsync(ListArchive listArchive, CancellationToken cancellationToken);
|
||||
}
|
||||
Task AddAsync(ListArchive listArchive, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
namespace FilterLists.Archival.Domain.ListArchives
|
||||
namespace FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
public class ListArchive
|
||||
{
|
||||
public class ListArchive
|
||||
public ListArchive(int id, IAsyncEnumerable<ListArchiveSegment> segments)
|
||||
{
|
||||
public ListArchive(int id, IAsyncEnumerable<ListArchiveSegment> segments)
|
||||
{
|
||||
Id = id;
|
||||
Segments = segments;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
public IAsyncEnumerable<ListArchiveSegment> Segments { get; }
|
||||
Id = id;
|
||||
Segments = segments;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
|
||||
public IAsyncEnumerable<ListArchiveSegment> Segments { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
namespace FilterLists.Archival.Domain.ListArchives
|
||||
namespace FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
public class ListArchiveSegment
|
||||
{
|
||||
public class ListArchiveSegment
|
||||
public ListArchiveSegment(Uri sourceUri, Stream content)
|
||||
{
|
||||
public ListArchiveSegment(Uri sourceUri, Stream content)
|
||||
{
|
||||
Extension = ListFileExtension.FromUri(sourceUri);
|
||||
Content = content;
|
||||
}
|
||||
|
||||
public ListFileExtension Extension { get; }
|
||||
|
||||
public Stream Content { get; }
|
||||
Extension = ListFileExtension.FromUri(sourceUri);
|
||||
Content = content;
|
||||
}
|
||||
|
||||
public ListFileExtension Extension { get; }
|
||||
|
||||
public Stream Content { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,63 @@
|
|||
using FilterLists.Archival.Domain.Extensions;
|
||||
using FilterLists.Archival.Domain.SeedWork;
|
||||
|
||||
namespace FilterLists.Archival.Domain.ListArchives
|
||||
namespace FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
public sealed class ListFileExtension : ValueObject
|
||||
{
|
||||
public sealed class ListFileExtension : ValueObject
|
||||
private static readonly IDictionary<string, (bool IsPlainText, bool IsMeaningfulToConsumer)> Info =
|
||||
new Dictionary<string, (bool, bool)>
|
||||
{
|
||||
{ string.Empty, (true, false) },
|
||||
{ ".txt", (true, false) },
|
||||
{ ".7z", (false, false) },
|
||||
{ ".action", (true, true) }, // Privoxy
|
||||
{ ".all", (true, false) },
|
||||
{ ".aspx", (true, false) },
|
||||
{ ".bat", (true, true) },
|
||||
{ ".blacklist", (true, false) },
|
||||
{ ".cidr", (true, true) },
|
||||
{ ".conf", (true, true) }, // dnsmasq / Unbound / BIND
|
||||
{ ".csv", (true, true) },
|
||||
{ ".dat", (true, true) }, // Halite for Windows
|
||||
{ ".deny", (true, true) },
|
||||
{ ".gz", (false, false) },
|
||||
{ ".hosts", (true, true) },
|
||||
{ ".ips", (true, false) },
|
||||
{ ".ipset", (true, true) }, // Firehol
|
||||
{ ".json", (true, true) },
|
||||
{ ".list", (true, false) },
|
||||
{ ".lsrules", (true, true) }, // Little Snitch
|
||||
{ ".md", (true, true) },
|
||||
{ ".netset", (true, true) }, // Firehol
|
||||
{ ".p2p", (true, true) }, // Peer Guardian
|
||||
{ ".php", (true, false) },
|
||||
{ ".raw", (true, false) },
|
||||
{ ".rpz", (true, true) }, // Response Policy Zone
|
||||
{ ".tpl", (true, true) }, // Internet Explorer
|
||||
{ ".uBl", (true, false) },
|
||||
{ ".zip", (false, false) },
|
||||
{ ".zone", (true, false) }
|
||||
};
|
||||
|
||||
private ListFileExtension(string value)
|
||||
{
|
||||
private static readonly IDictionary<string, (bool IsPlainText, bool IsMeaningfulToConsumer)> Info =
|
||||
new Dictionary<string, (bool, bool)>
|
||||
{
|
||||
{ string.Empty, (true, false) },
|
||||
{ ".txt", (true, false) },
|
||||
{ ".7z", (false, false) },
|
||||
{ ".action", (true, true) }, // Privoxy
|
||||
{ ".all", (true, false) },
|
||||
{ ".aspx", (true, false) },
|
||||
{ ".bat", (true, true) },
|
||||
{ ".blacklist", (true, false) },
|
||||
{ ".cidr", (true, true) },
|
||||
{ ".conf", (true, true) }, // dnsmasq / Unbound / BIND
|
||||
{ ".csv", (true, true) },
|
||||
{ ".dat", (true, true) }, // Halite for Windows
|
||||
{ ".deny", (true, true) },
|
||||
{ ".gz", (false, false) },
|
||||
{ ".hosts", (true, true) },
|
||||
{ ".ips", (true, false) },
|
||||
{ ".ipset", (true, true) }, // Firehol
|
||||
{ ".json", (true, true) },
|
||||
{ ".list", (true, false) },
|
||||
{ ".lsrules", (true, true) }, // Little Snitch
|
||||
{ ".md", (true, true) },
|
||||
{ ".netset", (true, true) }, // Firehol
|
||||
{ ".p2p", (true, true) }, // Peer Guardian
|
||||
{ ".php", (true, false) },
|
||||
{ ".raw", (true, false) },
|
||||
{ ".rpz", (true, true) }, // Response Policy Zone
|
||||
{ ".tpl", (true, true) }, // Internet Explorer
|
||||
{ ".uBl", (true, false) },
|
||||
{ ".zip", (false, false) },
|
||||
{ ".zone", (true, false) }
|
||||
};
|
||||
Value = value;
|
||||
}
|
||||
|
||||
private ListFileExtension(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
public string Value { get; }
|
||||
|
||||
public string Value { get; }
|
||||
public bool IsPlainText => Info[Value].IsPlainText;
|
||||
|
||||
public bool IsPlainText => Info[Value].IsPlainText;
|
||||
public bool IsMeaningfulToConsumer => Info[Value].IsMeaningfulToConsumer;
|
||||
|
||||
public bool IsMeaningfulToConsumer => Info[Value].IsMeaningfulToConsumer;
|
||||
public static ListFileExtension FromUri(Uri uri)
|
||||
{
|
||||
return new ListFileExtension(uri.GetFileExtension());
|
||||
}
|
||||
|
||||
public static ListFileExtension FromUri(Uri uri)
|
||||
{
|
||||
return new ListFileExtension(uri.GetFileExtension());
|
||||
}
|
||||
|
||||
protected override IEnumerable<object> GetEqualityComponents()
|
||||
{
|
||||
return new[] { Value };
|
||||
}
|
||||
protected override IEnumerable<object> GetEqualityComponents()
|
||||
{
|
||||
return new[] { Value };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
namespace FilterLists.Archival.Domain.SeedWork
|
||||
namespace FilterLists.Archival.Domain.SeedWork;
|
||||
|
||||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
public interface IUnitOfWork : IDisposable
|
||||
{
|
||||
void Commit();
|
||||
}
|
||||
void Commit();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,56 @@
|
|||
namespace FilterLists.Archival.Domain.SeedWork
|
||||
namespace FilterLists.Archival.Domain.SeedWork;
|
||||
|
||||
/// <remarks>https://enterprisecraftsmanship.com/posts/value-object-better-implementation/</remarks>
|
||||
public abstract class ValueObject
|
||||
{
|
||||
/// <remarks>https://enterprisecraftsmanship.com/posts/value-object-better-implementation/</remarks>
|
||||
public abstract class ValueObject
|
||||
protected abstract IEnumerable<object> GetEqualityComponents();
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
protected abstract IEnumerable<object> GetEqualityComponents();
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
if (obj == null)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueObject = (ValueObject)obj;
|
||||
|
||||
return GetEqualityComponents().SequenceEqual(valueObject.GetEqualityComponents());
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
if (GetType() != obj.GetType())
|
||||
{
|
||||
return GetEqualityComponents()
|
||||
.Aggregate(1, (current, obj) =>
|
||||
return false;
|
||||
}
|
||||
|
||||
var valueObject = (ValueObject)obj;
|
||||
|
||||
return GetEqualityComponents().SequenceEqual(valueObject.GetEqualityComponents());
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return GetEqualityComponents()
|
||||
.Aggregate(1, (current, obj) =>
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (current * 23) + (obj?.GetHashCode() ?? 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
return (current * 23) + (obj?.GetHashCode() ?? 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static bool operator ==(ValueObject? a, ValueObject? b)
|
||||
public static bool operator ==(ValueObject? a, ValueObject? b)
|
||||
{
|
||||
if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
|
||||
{
|
||||
if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.Equals(b);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(ValueObject? a, ValueObject? b)
|
||||
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
|
||||
{
|
||||
return !(a == b);
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(ValueObject? a, ValueObject? b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Polly;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Clients
|
||||
namespace FilterLists.Archival.Infrastructure.Clients;
|
||||
|
||||
internal static class ConfigurationExtensions
|
||||
{
|
||||
internal static class ConfigurationExtensions
|
||||
public static void AddClients(this IServiceCollection services)
|
||||
{
|
||||
public static void AddClients(this IServiceCollection services)
|
||||
{
|
||||
services.AddHttpClient<IHttpContentClient, HttpContentClient>()
|
||||
.AddTransientHttpErrorPolicy(b => b.WaitAndRetryAsync(new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)
|
||||
}));
|
||||
}
|
||||
services.AddHttpClient<IHttpContentClient, HttpContentClient>()
|
||||
.AddTransientHttpErrorPolicy(b => b.WaitAndRetryAsync(new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Clients
|
||||
namespace FilterLists.Archival.Infrastructure.Clients;
|
||||
|
||||
public interface IHttpContentClient : IDisposable
|
||||
{
|
||||
public interface IHttpContentClient : IDisposable
|
||||
Task<Stream> GetContentAsync(Uri url, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
internal sealed class HttpContentClient : IHttpContentClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ICollection<HttpResponseMessage> _httpResponseMessages = new HashSet<HttpResponseMessage>();
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HttpContentClient(HttpClient httpClient, ILogger<HttpContentClient> logger)
|
||||
{
|
||||
Task<Stream> GetContentAsync(Uri url, CancellationToken cancellationToken);
|
||||
_httpClient = httpClient;
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36");
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
internal sealed class HttpContentClient : IHttpContentClient
|
||||
public async Task<Stream> GetContentAsync(Uri url, CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ICollection<HttpResponseMessage> _httpResponseMessages = new HashSet<HttpResponseMessage>();
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public HttpContentClient(HttpClient httpClient, ILogger<HttpContentClient> logger)
|
||||
var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
_httpResponseMessages.Add(response);
|
||||
try
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_httpClient.DefaultRequestHeaders.Add("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36");
|
||||
_logger = logger;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Stream> GetContentAsync(Uri url, CancellationToken cancellationToken)
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
var response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
_httpResponseMessages.Add(response);
|
||||
try
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get content from {Url}", url);
|
||||
return Stream.Null;
|
||||
}
|
||||
_logger.LogError(ex, "Failed to get content from {Url}", url);
|
||||
return Stream.Null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var message in _httpResponseMessages)
|
||||
{
|
||||
foreach (var message in _httpResponseMessages)
|
||||
{
|
||||
message.Dispose();
|
||||
}
|
||||
message.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,28 +8,27 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure
|
||||
namespace FilterLists.Archival.Infrastructure;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static IHostBuilder UseInfrastructure(this IHostBuilder hostBuilder)
|
||||
{
|
||||
public static IHostBuilder UseInfrastructure(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.UseLogging();
|
||||
}
|
||||
return hostBuilder.UseLogging();
|
||||
}
|
||||
|
||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSharedKernelLogging(configuration);
|
||||
services.AddSchedulingServices(configuration);
|
||||
services.AddDirectoryApiClient(configuration);
|
||||
services.AddClients();
|
||||
services.AddPersistenceServices(configuration);
|
||||
}
|
||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSharedKernelLogging(configuration);
|
||||
services.AddSchedulingServices(configuration);
|
||||
services.AddDirectoryApiClient(configuration);
|
||||
services.AddClients();
|
||||
services.AddPersistenceServices(configuration);
|
||||
}
|
||||
|
||||
public static void UseInfrastructure(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseLogging();
|
||||
app.UseScheduling();
|
||||
}
|
||||
public static void UseInfrastructure(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseLogging();
|
||||
app.UseScheduling();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
namespace FilterLists.Archival.Infrastructure.Options
|
||||
{
|
||||
internal class GitOptions
|
||||
{
|
||||
public const string Key = "Git";
|
||||
namespace FilterLists.Archival.Infrastructure.Options;
|
||||
|
||||
public string RepositoryPath { get; init; } = null!;
|
||||
public string UserName { get; init; } = null!;
|
||||
public string UserEmail { get; init; } = null!;
|
||||
}
|
||||
internal class GitOptions
|
||||
{
|
||||
public const string Key = "Git";
|
||||
|
||||
public string RepositoryPath { get; init; } = null!;
|
||||
public string UserName { get; init; } = null!;
|
||||
public string UserEmail { get; init; } = null!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,23 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence
|
||||
{
|
||||
internal static class ConfigurationExtensions
|
||||
{
|
||||
public static void AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<GitOptions>(configuration.GetSection(GitOptions.Key));
|
||||
services.AddTransient<IRepository, Repository>(_ =>
|
||||
{
|
||||
var gitOptions = configuration.GetSection(GitOptions.Key).Get<GitOptions>();
|
||||
if (!Repository.IsValid(gitOptions.RepositoryPath))
|
||||
{
|
||||
Repository.Init(gitOptions.RepositoryPath);
|
||||
}
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence;
|
||||
|
||||
return new Repository(gitOptions.RepositoryPath);
|
||||
});
|
||||
services.AddTransient<IListArchiveRepository, GitListArchiveRepository>();
|
||||
}
|
||||
internal static class ConfigurationExtensions
|
||||
{
|
||||
public static void AddPersistenceServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<GitOptions>(configuration.GetSection(GitOptions.Key));
|
||||
services.AddTransient<IRepository, Repository>(_ =>
|
||||
{
|
||||
var gitOptions = configuration.GetSection(GitOptions.Key).Get<GitOptions>();
|
||||
if (!Repository.IsValid(gitOptions.RepositoryPath))
|
||||
{
|
||||
Repository.Init(gitOptions.RepositoryPath);
|
||||
}
|
||||
|
||||
return new Repository(gitOptions.RepositoryPath);
|
||||
});
|
||||
services.AddTransient<IListArchiveRepository, GitListArchiveRepository>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies;
|
||||
|
||||
internal interface IStreamToPlainTextConversionStrategy
|
||||
{
|
||||
internal interface IStreamToPlainTextConversionStrategy
|
||||
{
|
||||
Stream Convert(ListArchiveSegment listArchiveSegment, CancellationToken cancellationToken);
|
||||
}
|
||||
Stream Convert(ListArchiveSegment listArchiveSegment, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
using FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies;
|
||||
|
||||
internal class PlainText : IStreamToPlainTextConversionStrategy
|
||||
{
|
||||
internal class PlainText : IStreamToPlainTextConversionStrategy
|
||||
public Stream Convert(ListArchiveSegment listArchiveSegment, CancellationToken cancellationToken)
|
||||
{
|
||||
public Stream Convert(ListArchiveSegment listArchiveSegment, CancellationToken cancellationToken)
|
||||
{
|
||||
return listArchiveSegment.Content;
|
||||
}
|
||||
return listArchiveSegment.Content;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
using FilterLists.Archival.Domain.ListArchives;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence.FileWriteStrategies;
|
||||
|
||||
internal static class StreamToPlainTextConversionStrategyFactory
|
||||
{
|
||||
internal static class StreamToPlainTextConversionStrategyFactory
|
||||
public static TStrategy? GetStrategy<TStrategy>(this ListArchiveSegment segment)
|
||||
where TStrategy : class, IStreamToPlainTextConversionStrategy
|
||||
{
|
||||
public static TStrategy? GetStrategy<TStrategy>(this ListArchiveSegment segment)
|
||||
where TStrategy : class, IStreamToPlainTextConversionStrategy
|
||||
{
|
||||
// TODO: implement non-plain text strategies
|
||||
return segment.Extension.IsPlainText
|
||||
? new PlainText() as TStrategy
|
||||
: default;
|
||||
}
|
||||
// TODO: implement non-plain text strategies
|
||||
return segment.Extension.IsPlainText
|
||||
? new PlainText() as TStrategy
|
||||
: default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,124 +6,123 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence
|
||||
namespace FilterLists.Archival.Infrastructure.Persistence;
|
||||
|
||||
internal sealed class GitListArchiveRepository : IListArchiveRepository
|
||||
{
|
||||
internal sealed class GitListArchiveRepository : IListArchiveRepository
|
||||
private readonly ILogger _logger;
|
||||
private readonly GitOptions _options;
|
||||
private readonly IRepository _repo;
|
||||
private readonly ICollection<FileInfo> _writtenFiles = new HashSet<FileInfo>();
|
||||
|
||||
public GitListArchiveRepository(
|
||||
ILogger<GitListArchiveRepository> logger,
|
||||
IOptions<GitOptions> gitOptions,
|
||||
IRepository repository)
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly GitOptions _options;
|
||||
private readonly IRepository _repo;
|
||||
private readonly ICollection<FileInfo> _writtenFiles = new HashSet<FileInfo>();
|
||||
_logger = logger;
|
||||
_options = gitOptions.Value;
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
public GitListArchiveRepository(
|
||||
ILogger<GitListArchiveRepository> logger,
|
||||
IOptions<GitOptions> gitOptions,
|
||||
IRepository repository)
|
||||
public async Task AddAsync(ListArchive listArchive, CancellationToken cancellationToken)
|
||||
{
|
||||
var segmentNumber = 1;
|
||||
await foreach (var segment in listArchive.Segments.WithCancellation(cancellationToken))
|
||||
{
|
||||
_logger = logger;
|
||||
_options = gitOptions.Value;
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
public async Task AddAsync(ListArchive listArchive, CancellationToken cancellationToken)
|
||||
{
|
||||
var segmentNumber = 1;
|
||||
await foreach (var segment in listArchive.Segments.WithCancellation(cancellationToken))
|
||||
var strategy = segment.GetStrategy<IStreamToPlainTextConversionStrategy>();
|
||||
if (strategy is default(IStreamToPlainTextConversionStrategy))
|
||||
{
|
||||
var strategy = segment.GetStrategy<IStreamToPlainTextConversionStrategy>();
|
||||
if (strategy is default(IStreamToPlainTextConversionStrategy))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"No stream to plain text conversion strategy found for extension {Extension} for list {ListId}. Skipping list",
|
||||
segment.Extension,
|
||||
listArchive.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var fileInfo = GetTargetFile(listArchive.Id, segmentNumber, segment.Extension);
|
||||
if (fileInfo is default(FileInfo))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Writing from non-plain text extension {Extension} for list {ListId} not yet supported. Skipping list",
|
||||
segment.Extension,
|
||||
listArchive.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
_writtenFiles.Add(fileInfo);
|
||||
|
||||
_logger.LogInformation("Writing {FileName}", fileInfo.Name);
|
||||
|
||||
await using var target = fileInfo.OpenWrite();
|
||||
await strategy.Convert(segment, cancellationToken).CopyToAsync(target, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Finished writing {FileName}", fileInfo.Name);
|
||||
|
||||
segmentNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
if (_writtenFiles.Count > 0)
|
||||
{
|
||||
var fileNames = _writtenFiles.Select(f => f.Name).ToList();
|
||||
var signature = new Signature(_options.UserName, _options.UserEmail, DateTime.UtcNow);
|
||||
var message =
|
||||
$"feat(archives): archive {fileNames.Count} file(s){Environment.NewLine}{string.Join(Environment.NewLine, fileNames)}";
|
||||
Commands.Stage(_repo, fileNames);
|
||||
try
|
||||
{
|
||||
_repo.Commit(message, signature, signature);
|
||||
}
|
||||
catch (EmptyCommitException ex)
|
||||
{
|
||||
_logger.LogInformation(ex, "No changes to commit for {@FileNames}", fileNames);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Committed {@FileNames}", fileNames);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No written files to commit");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var file in _writtenFiles)
|
||||
{
|
||||
if (File.Exists(file.Name))
|
||||
{
|
||||
File.Delete(file.Name);
|
||||
}
|
||||
_logger.LogWarning(
|
||||
"No stream to plain text conversion strategy found for extension {Extension} for list {ListId}. Skipping list",
|
||||
segment.Extension,
|
||||
listArchive.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
_repo.CheckoutPaths("HEAD", _writtenFiles.Select(f => f.Name));
|
||||
}
|
||||
|
||||
private FileInfo? GetTargetFile(int listId, int segmentNumber, ListFileExtension extension)
|
||||
{
|
||||
string targetExtension;
|
||||
if (extension.IsPlainText)
|
||||
var fileInfo = GetTargetFile(listArchive.Id, segmentNumber, segment.Extension);
|
||||
if (fileInfo is default(FileInfo))
|
||||
{
|
||||
targetExtension = extension.IsMeaningfulToConsumer ? extension.Value : ".txt";
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement
|
||||
return default;
|
||||
_logger.LogWarning(
|
||||
"Writing from non-plain text extension {Extension} for list {ListId} not yet supported. Skipping list",
|
||||
segment.Extension,
|
||||
listArchive.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetFileName = GetTargetFileNamePrefix(listId) +
|
||||
(segmentNumber == 1 ? string.Empty : $"-{segmentNumber}") +
|
||||
targetExtension;
|
||||
return new FileInfo(Path.Combine(_options.RepositoryPath, targetFileName));
|
||||
}
|
||||
_writtenFiles.Add(fileInfo);
|
||||
|
||||
private static string GetTargetFileNamePrefix(int listId)
|
||||
{
|
||||
return listId.ToString(CultureInfo.InvariantCulture).PadLeft(5, '0');
|
||||
_logger.LogInformation("Writing {FileName}", fileInfo.Name);
|
||||
|
||||
await using var target = fileInfo.OpenWrite();
|
||||
await strategy.Convert(segment, cancellationToken).CopyToAsync(target, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Finished writing {FileName}", fileInfo.Name);
|
||||
|
||||
segmentNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
if (_writtenFiles.Count > 0)
|
||||
{
|
||||
var fileNames = _writtenFiles.Select(f => f.Name).ToList();
|
||||
var signature = new Signature(_options.UserName, _options.UserEmail, DateTime.UtcNow);
|
||||
var message =
|
||||
$"feat(archives): archive {fileNames.Count} file(s){Environment.NewLine}{string.Join(Environment.NewLine, fileNames)}";
|
||||
Commands.Stage(_repo, fileNames);
|
||||
try
|
||||
{
|
||||
_repo.Commit(message, signature, signature);
|
||||
}
|
||||
catch (EmptyCommitException ex)
|
||||
{
|
||||
_logger.LogInformation(ex, "No changes to commit for {@FileNames}", fileNames);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Committed {@FileNames}", fileNames);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No written files to commit");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var file in _writtenFiles)
|
||||
{
|
||||
if (File.Exists(file.Name))
|
||||
{
|
||||
File.Delete(file.Name);
|
||||
}
|
||||
}
|
||||
|
||||
_repo.CheckoutPaths("HEAD", _writtenFiles.Select(f => f.Name));
|
||||
}
|
||||
|
||||
private FileInfo? GetTargetFile(int listId, int segmentNumber, ListFileExtension extension)
|
||||
{
|
||||
string targetExtension;
|
||||
if (extension.IsPlainText)
|
||||
{
|
||||
targetExtension = extension.IsMeaningfulToConsumer ? extension.Value : ".txt";
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: implement
|
||||
return default;
|
||||
}
|
||||
|
||||
var targetFileName = GetTargetFileNamePrefix(listId) +
|
||||
(segmentNumber == 1 ? string.Empty : $"-{segmentNumber}") +
|
||||
targetExtension;
|
||||
return new FileInfo(Path.Combine(_options.RepositoryPath, targetFileName));
|
||||
}
|
||||
|
||||
private static string GetTargetFileNamePrefix(int listId)
|
||||
{
|
||||
return listId.ToString(CultureInfo.InvariantCulture).PadLeft(5, '0');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,20 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling;
|
||||
|
||||
internal static class ConfigurationExtensions
|
||||
{
|
||||
internal static class ConfigurationExtensions
|
||||
private static ConnectionMultiplexer _redis = null!;
|
||||
|
||||
public static void AddSchedulingServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
private static ConnectionMultiplexer _redis = null!;
|
||||
_redis = ConnectionMultiplexer.Connect(configuration.GetConnectionString("SchedulingConnection"));
|
||||
services.AddHangfire((_, globalConfiguration) => globalConfiguration.UseRedisStorage(_redis).UseMediatR());
|
||||
}
|
||||
|
||||
public static void AddSchedulingServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
_redis = ConnectionMultiplexer.Connect(configuration.GetConnectionString("SchedulingConnection"));
|
||||
services.AddHangfire((_, globalConfiguration) => globalConfiguration.UseRedisStorage(_redis).UseMediatR());
|
||||
}
|
||||
|
||||
public static void UseScheduling(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = Environment.ProcessorCount });
|
||||
}
|
||||
public static void UseScheduling(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = Environment.ProcessorCount });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
using Hangfire;
|
||||
using Hangfire.Storage;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling
|
||||
{
|
||||
public static class HangfireExtensions
|
||||
{
|
||||
// https://github.com/HangfireIO/Hangfire/issues/394#issuecomment-179924221
|
||||
public static void PurgeJobs(this IMonitoringApi monitoringApi)
|
||||
{
|
||||
var toDelete = new List<string>();
|
||||
foreach (var queue in monitoringApi.Queues())
|
||||
{
|
||||
for (var i = 0; i < Math.Ceiling(queue.Length / 1000d); i++)
|
||||
{
|
||||
toDelete.AddRange(monitoringApi.EnqueuedJobs(queue.Name, 1000 * i, 1000).Select(x => x.Key));
|
||||
}
|
||||
}
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling;
|
||||
|
||||
foreach (var jobId in toDelete)
|
||||
public static class HangfireExtensions
|
||||
{
|
||||
// https://github.com/HangfireIO/Hangfire/issues/394#issuecomment-179924221
|
||||
public static void PurgeJobs(this IMonitoringApi monitoringApi)
|
||||
{
|
||||
var toDelete = new List<string>();
|
||||
foreach (var queue in monitoringApi.Queues())
|
||||
{
|
||||
for (var i = 0; i < Math.Ceiling(queue.Length / 1000d); i++)
|
||||
{
|
||||
BackgroundJob.Delete(jobId);
|
||||
toDelete.AddRange(monitoringApi.EnqueuedJobs(queue.Name, 1000 * i, 1000).Select(x => x.Key));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var jobId in toDelete)
|
||||
{
|
||||
BackgroundJob.Delete(jobId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,36 +2,35 @@
|
|||
using MediatR;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling
|
||||
namespace FilterLists.Archival.Infrastructure.Scheduling;
|
||||
|
||||
public static class RequestExtensions
|
||||
{
|
||||
public static class RequestExtensions
|
||||
public static void EnqueueBackgroundJob(this IRequest request)
|
||||
{
|
||||
public static void EnqueueBackgroundJob(this IRequest request)
|
||||
{
|
||||
// Hangfire replaces CancellationToken at runtime with its own. We just need any in the signature.
|
||||
// https://docs.hangfire.io/en/latest/background-methods/using-cancellation-tokens.html#cancellationtoken
|
||||
BackgroundJob.Enqueue<IMediator>(m => m.Send(request, CancellationToken.None));
|
||||
}
|
||||
|
||||
public static void ScheduleBackgroundJob(this IRequest request, TimeSpan delay)
|
||||
{
|
||||
BackgroundJob.Schedule<IMediator>(m => m.Send(request, CancellationToken.None), delay);
|
||||
}
|
||||
|
||||
public static void AddOrUpdateRecurringJob(this IRequest request, Func<string> cronExpression)
|
||||
{
|
||||
RecurringJob.AddOrUpdate<IMediator>(m => m.Send(request, CancellationToken.None), cronExpression);
|
||||
}
|
||||
// Hangfire replaces CancellationToken at runtime with its own. We just need any in the signature.
|
||||
// https://docs.hangfire.io/en/latest/background-methods/using-cancellation-tokens.html#cancellationtoken
|
||||
BackgroundJob.Enqueue<IMediator>(m => m.Send(request, CancellationToken.None));
|
||||
}
|
||||
|
||||
internal static class HangfireExtension
|
||||
public static void ScheduleBackgroundJob(this IRequest request, TimeSpan delay)
|
||||
{
|
||||
public static IGlobalConfiguration UseMediatR(this IGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
/// https://codeopinion.com/background-commands-mediatr-hangfire/
|
||||
GlobalConfiguration.Configuration.UseSerializerSettings(
|
||||
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
|
||||
return globalConfiguration;
|
||||
}
|
||||
BackgroundJob.Schedule<IMediator>(m => m.Send(request, CancellationToken.None), delay);
|
||||
}
|
||||
|
||||
public static void AddOrUpdateRecurringJob(this IRequest request, Func<string> cronExpression)
|
||||
{
|
||||
RecurringJob.AddOrUpdate<IMediator>(m => m.Send(request, CancellationToken.None), cronExpression);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class HangfireExtension
|
||||
{
|
||||
public static IGlobalConfiguration UseMediatR(this IGlobalConfiguration globalConfiguration)
|
||||
{
|
||||
/// https://codeopinion.com/background-commands-mediatr-hangfire/
|
||||
GlobalConfiguration.Configuration.UseSerializerSettings(
|
||||
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
|
||||
return globalConfiguration;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,23 @@
|
|||
using Polly;
|
||||
using Refit;
|
||||
|
||||
namespace FilterLists.Directory.Api.Contracts
|
||||
namespace FilterLists.Directory.Api.Contracts;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static void AddDirectoryApiClient(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
public static void AddDirectoryApiClient(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// TODO: use SystemTextJsonContentSerializer() once less feature-limited
|
||||
services.AddRefitClient<IDirectoryApi>()
|
||||
.ConfigureHttpClient(c =>
|
||||
// TODO: use SystemTextJsonContentSerializer() once less feature-limited
|
||||
services.AddRefitClient<IDirectoryApi>()
|
||||
.ConfigureHttpClient(c =>
|
||||
{
|
||||
var host = configuration.GetSection(ApiOptions.Key).Get<ApiOptions>().DirectoryHost;
|
||||
c.BaseAddress = new UriBuilder("http", host).Uri;
|
||||
})
|
||||
.AddTransientHttpErrorPolicy(b =>
|
||||
b.WaitAndRetryAsync(new[]
|
||||
{
|
||||
var host = configuration.GetSection(ApiOptions.Key).Get<ApiOptions>().DirectoryHost;
|
||||
c.BaseAddress = new UriBuilder("http", host).Uri;
|
||||
})
|
||||
.AddTransientHttpErrorPolicy(b =>
|
||||
b.WaitAndRetryAsync(new[]
|
||||
{
|
||||
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)
|
||||
}));
|
||||
}
|
||||
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
using FilterLists.Directory.Api.Contracts.Models;
|
||||
using Refit;
|
||||
|
||||
namespace FilterLists.Directory.Api.Contracts
|
||||
{
|
||||
public interface IDirectoryApi
|
||||
{
|
||||
[Get("/lists")]
|
||||
Task<IEnumerable<ListVm>> GetListsAsync(CancellationToken cancellationToken);
|
||||
namespace FilterLists.Directory.Api.Contracts;
|
||||
|
||||
[Get("/lists/{id}")]
|
||||
Task<ListDetailsVm> GetListDetailsAsync(int id, CancellationToken cancellationToken);
|
||||
}
|
||||
public interface IDirectoryApi
|
||||
{
|
||||
[Get("/lists")]
|
||||
Task<IEnumerable<ListVm>> GetListsAsync(CancellationToken cancellationToken);
|
||||
|
||||
[Get("/lists/{id}")]
|
||||
Task<ListDetailsVm> GetListDetailsAsync(int id, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,36 @@
|
|||
namespace FilterLists.Directory.Api.Contracts.Models
|
||||
{
|
||||
public class ListDetailsVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
public IEnumerable<string>? Iso6391s { get; init; }
|
||||
public IEnumerable<int>? TagIds { get; init; }
|
||||
public IEnumerable<ViewUrlVm>? ViewUrls { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? OnionUrl { get; init; }
|
||||
public Uri? PolicyUrl { get; init; }
|
||||
public Uri? SubmissionUrl { get; init; }
|
||||
public Uri? IssuesUrl { get; init; }
|
||||
public Uri? ForumUrl { get; init; }
|
||||
public Uri? ChatUrl { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public Uri? DonateUrl { get; init; }
|
||||
public IEnumerable<int>? MaintainerIds { get; init; }
|
||||
public IEnumerable<int>? UpstreamFilterListIds { get; init; }
|
||||
public IEnumerable<int>? ForkFilterListIds { get; init; }
|
||||
public IEnumerable<int>? IncludedInFilterListIds { get; init; }
|
||||
public IEnumerable<int>? IncludesFilterListIds { get; init; }
|
||||
public IEnumerable<int>? DependencyFilterListIds { get; init; }
|
||||
public IEnumerable<int>? DependentFilterListIds { get; init; }
|
||||
namespace FilterLists.Directory.Api.Contracts.Models;
|
||||
|
||||
public class ViewUrlVm
|
||||
{
|
||||
public short SegmentNumber { get; init; }
|
||||
public short Primariness { get; init; }
|
||||
public Uri Url { get; init; } = null!;
|
||||
}
|
||||
public class ListDetailsVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
public IEnumerable<string>? Iso6391s { get; init; }
|
||||
public IEnumerable<int>? TagIds { get; init; }
|
||||
public IEnumerable<ViewUrlVm>? ViewUrls { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? OnionUrl { get; init; }
|
||||
public Uri? PolicyUrl { get; init; }
|
||||
public Uri? SubmissionUrl { get; init; }
|
||||
public Uri? IssuesUrl { get; init; }
|
||||
public Uri? ForumUrl { get; init; }
|
||||
public Uri? ChatUrl { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public Uri? DonateUrl { get; init; }
|
||||
public IEnumerable<int>? MaintainerIds { get; init; }
|
||||
public IEnumerable<int>? UpstreamFilterListIds { get; init; }
|
||||
public IEnumerable<int>? ForkFilterListIds { get; init; }
|
||||
public IEnumerable<int>? IncludedInFilterListIds { get; init; }
|
||||
public IEnumerable<int>? IncludesFilterListIds { get; init; }
|
||||
public IEnumerable<int>? DependencyFilterListIds { get; init; }
|
||||
public IEnumerable<int>? DependentFilterListIds { get; init; }
|
||||
|
||||
public class ViewUrlVm
|
||||
{
|
||||
public short SegmentNumber { get; init; }
|
||||
public short Primariness { get; init; }
|
||||
public Uri Url { get; init; } = null!;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
namespace FilterLists.Directory.Api.Contracts.Models
|
||||
namespace FilterLists.Directory.Api.Contracts.Models;
|
||||
|
||||
public class ListVm
|
||||
{
|
||||
public class ListVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
public IEnumerable<string>? Iso6391s { get; init; }
|
||||
public IEnumerable<int>? TagIds { get; init; }
|
||||
public Uri? PrimaryViewUrl { get; init; }
|
||||
public IEnumerable<int>? MaintainerIds { get; init; }
|
||||
}
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
public IEnumerable<string>? Iso6391s { get; init; }
|
||||
public IEnumerable<int>? TagIds { get; init; }
|
||||
public Uri? PrimaryViewUrl { get; init; }
|
||||
public IEnumerable<int>? MaintainerIds { get; init; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
namespace FilterLists.Directory.Api.Contracts.Options
|
||||
{
|
||||
internal class ApiOptions
|
||||
{
|
||||
public const string Key = "Api";
|
||||
namespace FilterLists.Directory.Api.Contracts.Options;
|
||||
|
||||
public string DirectoryHost { get; init; } = null!;
|
||||
}
|
||||
internal class ApiOptions
|
||||
{
|
||||
public const string Key = "Api";
|
||||
|
||||
public string DirectoryHost { get; init; } = null!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,34 +2,33 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public abstract class BaseController : ControllerBase
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public abstract class BaseController : ControllerBase
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
protected BaseController(IMemoryCache cache)
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
protected BaseController(IMemoryCache cache)
|
||||
/// <remarks>https://stackoverflow.com/a/52506210/2343739</remarks>
|
||||
protected async Task<IActionResult> CacheGetOrCreateAsync<TResponse>(
|
||||
Func<Task<TResponse>> actionAsync,
|
||||
int? keySuffix = default,
|
||||
TimeSpan? absoluteExpirationRelativeToNow = default,
|
||||
[CallerMemberName] string key = default!)
|
||||
{
|
||||
var cacheKey = $"{GetType().Name}_{key}{(keySuffix is null ? string.Empty : $"_{keySuffix}")}";
|
||||
var result = await _cache.GetOrCreateAsync(cacheKey, entry =>
|
||||
{
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
/// <remarks>https://stackoverflow.com/a/52506210/2343739</remarks>
|
||||
protected async Task<IActionResult> CacheGetOrCreateAsync<TResponse>(
|
||||
Func<Task<TResponse>> actionAsync,
|
||||
int? keySuffix = default,
|
||||
TimeSpan? absoluteExpirationRelativeToNow = default,
|
||||
[CallerMemberName] string key = default!)
|
||||
{
|
||||
var cacheKey = $"{GetType().Name}_{key}{(keySuffix is null ? string.Empty : $"_{keySuffix}")}";
|
||||
var result = await _cache.GetOrCreateAsync(cacheKey, entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
|
||||
return actionAsync();
|
||||
});
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
entry.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
|
||||
return actionAsync();
|
||||
});
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
// TODO: de-duplicate into SharedKernel
|
||||
public class ErrorController : ControllerBase
|
||||
{
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
// TODO: de-duplicate into SharedKernel
|
||||
public class ErrorController : ControllerBase
|
||||
[Route("/error-local-development")]
|
||||
public IActionResult ErrorLocalDevelopment([FromServices] IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
[Route("/error-local-development")]
|
||||
public IActionResult ErrorLocalDevelopment([FromServices] IWebHostEnvironment webHostEnvironment)
|
||||
if (webHostEnvironment.EnvironmentName != "Development")
|
||||
{
|
||||
if (webHostEnvironment.EnvironmentName != "Development")
|
||||
{
|
||||
throw new InvalidOperationException("This shouldn't be invoked in non-development environments.");
|
||||
}
|
||||
|
||||
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
return Problem(context?.Error.StackTrace, title: context?.Error.Message);
|
||||
throw new InvalidOperationException("This shouldn't be invoked in non-development environments.");
|
||||
}
|
||||
|
||||
[Route("/error")]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return Problem();
|
||||
}
|
||||
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
return Problem(context?.Error.StackTrace, title: context?.Error.Message);
|
||||
}
|
||||
|
||||
[Route("/error")]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return Problem();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class LanguagesController : BaseController
|
||||
{
|
||||
public class LanguagesController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public LanguagesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public LanguagesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the languages targeted by FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The languages targeted by FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetLanguages.LanguageVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLanguages.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the languages targeted by FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The languages targeted by FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetLanguages.LanguageVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLanguages.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class LicensesController : BaseController
|
||||
{
|
||||
public class LicensesController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public LicensesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public LicensesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the licenses applied to FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The licenses applied to FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetLicenses.LicenseVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLicenses.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the licenses applied to FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The licenses applied to FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetLicenses.LicenseVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLicenses.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,41 +4,40 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class ListsController : BaseController
|
||||
{
|
||||
public class ListsController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public ListsController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public ListsController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the FilterLists..
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ListVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLists.Query(), cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FilterLists..
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<ListVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLists.Query(), cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of the FilterList.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the FilterList.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The details of the FilterList.</returns>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(ListDetailsVm), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetDetails(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetListDetails.Query(id), cancellationToken), id);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the details of the FilterList.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the FilterList.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The details of the FilterList.</returns>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(ListDetailsVm), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetDetails(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetListDetails.Query(id), cancellationToken), id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class MaintainersController : BaseController
|
||||
{
|
||||
public class MaintainersController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public MaintainersController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public MaintainersController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maintainers of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The maintainers of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetMaintainers.MaintainerVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetMaintainers.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the maintainers of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The maintainers of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetMaintainers.MaintainerVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetMaintainers.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class SoftwareController : BaseController
|
||||
{
|
||||
public class SoftwareController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public SoftwareController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public SoftwareController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the software that subscribes to FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The software that subscribes to FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetSoftware.SoftwareVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetSoftware.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the software that subscribes to FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The software that subscribes to FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetSoftware.SoftwareVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetSoftware.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class SyntaxesController : BaseController
|
||||
{
|
||||
public class SyntaxesController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public SyntaxesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public SyntaxesController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the syntaxes of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The syntaxes of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetSyntaxes.SyntaxVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetSyntaxes.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the syntaxes of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The syntaxes of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetSyntaxes.SyntaxVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetSyntaxes.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,27 +3,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace FilterLists.Directory.Api.Controllers
|
||||
namespace FilterLists.Directory.Api.Controllers;
|
||||
|
||||
public class TagsController : BaseController
|
||||
{
|
||||
public class TagsController : BaseController
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public TagsController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public TagsController(IMemoryCache cache, IMediator mediator) : base(cache)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tags of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The tags of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetTags.TagVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetTags.Query(), cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the tags of FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The tags of FilterLists.</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<GetTags.TagVm>), StatusCodes.Status200OK)]
|
||||
public Task<IActionResult> Get(CancellationToken cancellationToken)
|
||||
{
|
||||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetTags.Query(), cancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,21 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence;
|
||||
using FilterLists.SharedKernel.Logging;
|
||||
|
||||
namespace FilterLists.Directory.Api
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// TODO: migrate to new hosting model https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio#new-hosting-model
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
await host.TryRunWithLoggingAsync(async () => await host.MigrateAsync());
|
||||
}
|
||||
namespace FilterLists.Directory.Api;
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
{
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseApplication()
|
||||
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
|
||||
}
|
||||
public static class Program
|
||||
{
|
||||
// TODO: migrate to new hosting model https://docs.microsoft.com/en-us/aspnet/core/migration/50-to-60?view=aspnetcore-6.0&tabs=visual-studio#new-hosting-model
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
await host.TryRunWithLoggingAsync(async () => await host.MigrateAsync());
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
{
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseApplication()
|
||||
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,33 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using FilterLists.Directory.Application;
|
||||
|
||||
namespace FilterLists.Directory.Api
|
||||
namespace FilterLists.Directory.Api;
|
||||
|
||||
internal class Startup
|
||||
{
|
||||
internal class Startup
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMemoryCache();
|
||||
services.AddRouting(o => o.LowercaseUrls = true);
|
||||
services.AddControllers().AddJsonOptions(o =>
|
||||
o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull);
|
||||
services.AddSwaggerGen();
|
||||
services.AddApplicationServices(Configuration);
|
||||
}
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMemoryCache();
|
||||
services.AddRouting(o => o.LowercaseUrls = true);
|
||||
services.AddControllers().AddJsonOptions(o =>
|
||||
o.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull);
|
||||
services.AddSwaggerGen();
|
||||
services.AddApplicationServices(Configuration);
|
||||
}
|
||||
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseApplication();
|
||||
app.UseExceptionHandler(env.IsDevelopment() ? "/error-local-development" : "/error");
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(e => e.MapControllers());
|
||||
app.UseSwagger();
|
||||
}
|
||||
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseApplication();
|
||||
app.UseExceptionHandler(env.IsDevelopment() ? "/error-local-development" : "/error");
|
||||
app.UseRouting();
|
||||
app.UseEndpoints(e => e.MapControllers());
|
||||
app.UseSwagger();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,43 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace FilterLists.Directory.Api
|
||||
namespace FilterLists.Directory.Api;
|
||||
|
||||
internal static class SwaggerExtensions
|
||||
{
|
||||
internal static class SwaggerExtensions
|
||||
public static void AddSwaggerGen(this IServiceCollection services)
|
||||
{
|
||||
public static void AddSwaggerGen(this IServiceCollection services)
|
||||
services.AddSwaggerGen(o =>
|
||||
{
|
||||
services.AddSwaggerGen(o =>
|
||||
o.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
o.SwaggerDoc("v1", new OpenApiInfo
|
||||
Title = "FilterLists Directory API",
|
||||
Description = "An ASP.NET Core API serving the core FilterList information.",
|
||||
Version = "v1",
|
||||
//TermsOfService = "",
|
||||
Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") },
|
||||
License = new OpenApiLicense
|
||||
{
|
||||
Title = "FilterLists Directory API",
|
||||
Description = "An ASP.NET Core API serving the core FilterList information.",
|
||||
Version = "v1",
|
||||
//TermsOfService = "",
|
||||
Contact = new OpenApiContact { Name = "FilterLists", Url = new Uri("https://filterlists.com") },
|
||||
License = new OpenApiLicense
|
||||
{
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/master/LICENSE")
|
||||
}
|
||||
});
|
||||
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
o.IncludeXmlComments(xmlPath);
|
||||
Name = "MIT License",
|
||||
Url = new Uri("https://github.com/collinbarrett/FilterLists/blob/master/LICENSE")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseSwagger(this IApplicationBuilder app)
|
||||
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
||||
o.IncludeXmlComments(xmlPath);
|
||||
});
|
||||
}
|
||||
|
||||
public static void UseSwagger(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseSwagger(o =>
|
||||
{
|
||||
app.UseSwagger(o =>
|
||||
o.RouteTemplate = "{documentName}/swagger.json";
|
||||
o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Servers = new List<OpenApiServer>
|
||||
{
|
||||
o.RouteTemplate = "{documentName}/swagger.json";
|
||||
o.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Servers = new List<OpenApiServer>
|
||||
{
|
||||
new() { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/api/directory" }
|
||||
});
|
||||
new() { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}/api/directory" }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,24 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Directory.Application
|
||||
namespace FilterLists.Directory.Application;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static IHostBuilder UseApplication(this IHostBuilder hostBuilder)
|
||||
{
|
||||
public static IHostBuilder UseApplication(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.UseInfrastructure();
|
||||
}
|
||||
return hostBuilder.UseInfrastructure();
|
||||
}
|
||||
|
||||
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddAutoMapper(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddInfrastructureServices(configuration);
|
||||
}
|
||||
public static void AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddAutoMapper(typeof(ConfigurationExtensions).Assembly);
|
||||
services.AddInfrastructureServices(configuration);
|
||||
}
|
||||
|
||||
public static void UseApplication(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseInfrastructure();
|
||||
}
|
||||
public static void UseApplication(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseInfrastructure();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,54 +5,53 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLanguages
|
||||
{
|
||||
public static class GetLanguages
|
||||
public class Query : IRequest<IEnumerable<LanguageVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<LanguageVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<LanguageVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<LanguageVm>>
|
||||
public async Task<IEnumerable<LanguageVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LanguageVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Languages
|
||||
.Where(l => l.FilterListLanguages.Any())
|
||||
.OrderBy(l => l.Iso6391)
|
||||
.ProjectTo<LanguageVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class LanguageVmProfile : Profile
|
||||
{
|
||||
public LanguageVmProfile()
|
||||
{
|
||||
CreateMap<Language, LanguageVm>()
|
||||
.ForMember(l => l.FilterListIds,
|
||||
o => o.MapFrom(l =>
|
||||
l.FilterListLanguages.Select(fll => fll.FilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class LanguageVm
|
||||
{
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public string Name { get; init; } = null!;
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
return await _context.Languages
|
||||
.Where(l => l.FilterListLanguages.Any())
|
||||
.OrderBy(l => l.Iso6391)
|
||||
.ProjectTo<LanguageVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class LanguageVmProfile : Profile
|
||||
{
|
||||
public LanguageVmProfile()
|
||||
{
|
||||
CreateMap<Language, LanguageVm>()
|
||||
.ForMember(l => l.FilterListIds,
|
||||
o => o.MapFrom(l =>
|
||||
l.FilterListLanguages.Select(fll => fll.FilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class LanguageVm
|
||||
{
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public string Name { get; init; } = null!;
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,56 +5,55 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLicenses
|
||||
{
|
||||
public static class GetLicenses
|
||||
public class Query : IRequest<IEnumerable<LicenseVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<LicenseVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<LicenseVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<LicenseVm>>
|
||||
public async Task<IEnumerable<LicenseVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LicenseVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Licenses
|
||||
.OrderBy(l => l.Id)
|
||||
.ProjectTo<LicenseVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class LicenseVmProfile : Profile
|
||||
{
|
||||
public LicenseVmProfile()
|
||||
{
|
||||
CreateMap<License, LicenseVm>()
|
||||
.ForMember(l => l.FilterListIds,
|
||||
o => o.MapFrom(l =>
|
||||
l.FilterLists.Select(fl => fl.Id).OrderBy(flid => flid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class LicenseVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public bool PermitsModification { get; init; }
|
||||
public bool PermitsDistribution { get; init; }
|
||||
public bool PermitsCommercialUse { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
return await _context.Licenses
|
||||
.OrderBy(l => l.Id)
|
||||
.ProjectTo<LicenseVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class LicenseVmProfile : Profile
|
||||
{
|
||||
public LicenseVmProfile()
|
||||
{
|
||||
CreateMap<License, LicenseVm>()
|
||||
.ForMember(l => l.FilterListIds,
|
||||
o => o.MapFrom(l =>
|
||||
l.FilterLists.Select(fl => fl.Id).OrderBy(flid => flid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class LicenseVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public bool PermitsModification { get; init; }
|
||||
public bool PermitsDistribution { get; init; }
|
||||
public bool PermitsCommercialUse { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,89 +6,88 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetListDetails
|
||||
{
|
||||
public static class GetListDetails
|
||||
public class Query : IRequest<ListDetailsVm?>
|
||||
{
|
||||
public class Query : IRequest<ListDetailsVm?>
|
||||
public Query(int id)
|
||||
{
|
||||
public Query(int id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public int Id { get; }
|
||||
Id = id;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, ListDetailsVm?>
|
||||
public int Id { get; }
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, ListDetailsVm?>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<ListDetailsVm?> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.FilterLists
|
||||
.ProjectTo<ListDetailsVm>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync(fl => fl.Id == request.Id, cancellationToken);
|
||||
}
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class ListDetailsVmProfile : Profile
|
||||
public async Task<ListDetailsVm?> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
public ListDetailsVmProfile()
|
||||
{
|
||||
CreateMap<FilterList, ListDetailsVm>()
|
||||
.ForMember(fl => fl.SyntaxIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListSyntaxes.Select(fls => fls.SyntaxId).OrderBy(sid => sid).AsEnumerable()))
|
||||
.ForMember(fl => fl.Iso6391s,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListLanguages.Select(fls => fls.Iso6391).OrderBy(i => i).AsEnumerable()))
|
||||
.ForMember(fl => fl.TagIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListTags.Select(flt => flt.TagId).OrderBy(tid => tid).AsEnumerable()))
|
||||
.ForMember(fl => fl.ViewUrls,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ViewUrls.OrderBy(u => u.SegmentNumber).ThenBy(u => u.Primariness).AsEnumerable()))
|
||||
.ForMember(fl => fl.MaintainerIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListMaintainers.Select(flm => flm.MaintainerId).OrderBy(mid => mid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.UpstreamFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.UpstreamFilterLists.Select(ufl => ufl.UpstreamFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.ForkFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ForkFilterLists.Select(ffl => ffl.ForkFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.IncludedInFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.IncludedInFilterLists.Select(iifl => iifl.IncludedInFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.IncludesFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.IncludesFilterLists.Select(ifl => ifl.IncludesFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.DependencyFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.DependencyFilterLists.Select(dfl => dfl.DependencyFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.DependentFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.DependentFilterLists.Select(dfl => dfl.DependentFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
return await _context.FilterLists
|
||||
.ProjectTo<ListDetailsVm>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync(fl => fl.Id == request.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
CreateMap<FilterListViewUrl, ListDetailsVm.ViewUrlVm>();
|
||||
}
|
||||
internal class ListDetailsVmProfile : Profile
|
||||
{
|
||||
public ListDetailsVmProfile()
|
||||
{
|
||||
CreateMap<FilterList, ListDetailsVm>()
|
||||
.ForMember(fl => fl.SyntaxIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListSyntaxes.Select(fls => fls.SyntaxId).OrderBy(sid => sid).AsEnumerable()))
|
||||
.ForMember(fl => fl.Iso6391s,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListLanguages.Select(fls => fls.Iso6391).OrderBy(i => i).AsEnumerable()))
|
||||
.ForMember(fl => fl.TagIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListTags.Select(flt => flt.TagId).OrderBy(tid => tid).AsEnumerable()))
|
||||
.ForMember(fl => fl.ViewUrls,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ViewUrls.OrderBy(u => u.SegmentNumber).ThenBy(u => u.Primariness).AsEnumerable()))
|
||||
.ForMember(fl => fl.MaintainerIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListMaintainers.Select(flm => flm.MaintainerId).OrderBy(mid => mid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.UpstreamFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.UpstreamFilterLists.Select(ufl => ufl.UpstreamFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.ForkFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ForkFilterLists.Select(ffl => ffl.ForkFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.IncludedInFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.IncludedInFilterLists.Select(iifl => iifl.IncludedInFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.IncludesFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.IncludesFilterLists.Select(ifl => ifl.IncludesFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.DependencyFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.DependencyFilterLists.Select(dfl => dfl.DependencyFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()))
|
||||
.ForMember(fl => fl.DependentFilterListIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.DependentFilterLists.Select(dfl => dfl.DependentFilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
|
||||
CreateMap<FilterListViewUrl, ListDetailsVm.ViewUrlVm>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,59 +6,58 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLists
|
||||
{
|
||||
public static class GetLists
|
||||
public class Query : IRequest<IEnumerable<ListVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<ListVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<ListVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<ListVm>>
|
||||
public async Task<IEnumerable<ListVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ListVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.FilterLists
|
||||
.OrderBy(fl => fl.Id)
|
||||
.ProjectTo<ListVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
return await _context.FilterLists
|
||||
.OrderBy(fl => fl.Id)
|
||||
.ProjectTo<ListVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ListVmProfile : Profile
|
||||
internal class ListVmProfile : Profile
|
||||
{
|
||||
public ListVmProfile()
|
||||
{
|
||||
public ListVmProfile()
|
||||
{
|
||||
CreateMap<FilterList, ListVm>()
|
||||
.ForMember(fl => fl.SyntaxIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListSyntaxes.Select(fls => fls.SyntaxId).OrderBy(sid => sid).AsEnumerable()))
|
||||
.ForMember(fl => fl.Iso6391s,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListLanguages.Select(fls => fls.Iso6391).OrderBy(i => i).AsEnumerable()))
|
||||
.ForMember(fl => fl.TagIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListTags.Select(flt => flt.TagId).OrderBy(tid => tid).AsEnumerable()))
|
||||
.ForMember(fl => fl.PrimaryViewUrl,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ViewUrls.OrderBy(u => u.SegmentNumber).ThenBy(u => u.Primariness).Select(u => u.Url)
|
||||
.FirstOrDefault()))
|
||||
.ForMember(fl => fl.MaintainerIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListMaintainers.Select(flm => flm.MaintainerId).OrderBy(mid => mid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
CreateMap<FilterList, ListVm>()
|
||||
.ForMember(fl => fl.SyntaxIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListSyntaxes.Select(fls => fls.SyntaxId).OrderBy(sid => sid).AsEnumerable()))
|
||||
.ForMember(fl => fl.Iso6391s,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListLanguages.Select(fls => fls.Iso6391).OrderBy(i => i).AsEnumerable()))
|
||||
.ForMember(fl => fl.TagIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListTags.Select(flt => flt.TagId).OrderBy(tid => tid).AsEnumerable()))
|
||||
.ForMember(fl => fl.PrimaryViewUrl,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.ViewUrls.OrderBy(u => u.SegmentNumber).ThenBy(u => u.Primariness).Select(u => u.Url)
|
||||
.FirstOrDefault()))
|
||||
.ForMember(fl => fl.MaintainerIds,
|
||||
o => o.MapFrom(fl =>
|
||||
fl.FilterListMaintainers.Select(flm => flm.MaintainerId).OrderBy(mid => mid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,56 +5,55 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetMaintainers
|
||||
{
|
||||
public static class GetMaintainers
|
||||
public class Query : IRequest<IEnumerable<MaintainerVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<MaintainerVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<MaintainerVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<MaintainerVm>>
|
||||
public async Task<IEnumerable<MaintainerVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MaintainerVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Maintainers
|
||||
.OrderBy(m => m.Id)
|
||||
.ProjectTo<MaintainerVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MaintainerVmProfile : Profile
|
||||
{
|
||||
public MaintainerVmProfile()
|
||||
{
|
||||
CreateMap<Maintainer, MaintainerVm>()
|
||||
.ForMember(m => m.FilterListIds,
|
||||
o => o.MapFrom(m =>
|
||||
m.FilterListMaintainers.Select(flm => flm.FilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class MaintainerVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public string? TwitterHandle { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
return await _context.Maintainers
|
||||
.OrderBy(m => m.Id)
|
||||
.ProjectTo<MaintainerVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MaintainerVmProfile : Profile
|
||||
{
|
||||
public MaintainerVmProfile()
|
||||
{
|
||||
CreateMap<Maintainer, MaintainerVm>()
|
||||
.ForMember(m => m.FilterListIds,
|
||||
o => o.MapFrom(m =>
|
||||
m.FilterListMaintainers.Select(flm => flm.FilterListId).OrderBy(flid => flid)
|
||||
.AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class MaintainerVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public string? TwitterHandle { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,56 +5,55 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetSoftware
|
||||
{
|
||||
public static class GetSoftware
|
||||
public class Query : IRequest<IEnumerable<SoftwareVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<SoftwareVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<SoftwareVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<SoftwareVm>>
|
||||
public async Task<IEnumerable<SoftwareVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SoftwareVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Software
|
||||
.OrderBy(s => s.Id)
|
||||
.ProjectTo<SoftwareVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SoftwareVmProfile : Profile
|
||||
{
|
||||
public SoftwareVmProfile()
|
||||
{
|
||||
CreateMap<Software, SoftwareVm>()
|
||||
.ForMember(s => s.SyntaxIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.SoftwareSyntaxes.Select(ss => ss.SyntaxId).OrderBy(sid => sid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class SoftwareVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
public bool SupportsAbpUrlScheme { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
return await _context.Software
|
||||
.OrderBy(s => s.Id)
|
||||
.ProjectTo<SoftwareVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SoftwareVmProfile : Profile
|
||||
{
|
||||
public SoftwareVmProfile()
|
||||
{
|
||||
CreateMap<Software, SoftwareVm>()
|
||||
.ForMember(s => s.SyntaxIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.SoftwareSyntaxes.Select(ss => ss.SyntaxId).OrderBy(sid => sid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class SoftwareVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
public bool SupportsAbpUrlScheme { get; init; }
|
||||
public IEnumerable<int>? SyntaxIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,58 +5,57 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetSyntaxes
|
||||
{
|
||||
public static class GetSyntaxes
|
||||
public class Query : IRequest<IEnumerable<SyntaxVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<SyntaxVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<SyntaxVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<SyntaxVm>>
|
||||
public async Task<IEnumerable<SyntaxVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SyntaxVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Syntaxes
|
||||
.OrderBy(s => s.Id)
|
||||
.ProjectTo<SyntaxVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyntaxVmProfile : Profile
|
||||
{
|
||||
public SyntaxVmProfile()
|
||||
{
|
||||
CreateMap<Syntax, SyntaxVm>()
|
||||
.ForMember(s => s.FilterListIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.FilterListSyntaxes.Select(sls => sls.FilterListId).OrderBy(flid => flid).AsEnumerable()))
|
||||
.ForMember(s => s.SoftwareIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.SoftwareSyntaxes.Select(ss => ss.SoftwareId).OrderBy(sid => sid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class SyntaxVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? Url { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
public IEnumerable<int>? SoftwareIds { get; init; }
|
||||
return await _context.Syntaxes
|
||||
.OrderBy(s => s.Id)
|
||||
.ProjectTo<SyntaxVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SyntaxVmProfile : Profile
|
||||
{
|
||||
public SyntaxVmProfile()
|
||||
{
|
||||
CreateMap<Syntax, SyntaxVm>()
|
||||
.ForMember(s => s.FilterListIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.FilterListSyntaxes.Select(sls => sls.FilterListId).OrderBy(flid => flid).AsEnumerable()))
|
||||
.ForMember(s => s.SoftwareIds,
|
||||
o => o.MapFrom(s =>
|
||||
s.SoftwareSyntaxes.Select(ss => ss.SoftwareId).OrderBy(sid => sid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class SyntaxVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? Url { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
public IEnumerable<int>? SoftwareIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,53 +5,52 @@
|
|||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetTags
|
||||
{
|
||||
public static class GetTags
|
||||
public class Query : IRequest<IEnumerable<TagVm>>
|
||||
{
|
||||
public class Query : IRequest<IEnumerable<TagVm>>
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<TagVm>>
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Query, IEnumerable<TagVm>>
|
||||
public async Task<IEnumerable<TagVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
private readonly IQueryContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public Handler(IQueryContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TagVm>> Handle(
|
||||
Query request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _context.Tags
|
||||
.OrderBy(t => t.Id)
|
||||
.ProjectTo<TagVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TagVmProfile : Profile
|
||||
{
|
||||
public TagVmProfile()
|
||||
{
|
||||
CreateMap<Tag, TagVm>()
|
||||
.ForMember(t => t.FilterListIds,
|
||||
o => o.MapFrom(t =>
|
||||
t.FilterListTags.Select(flt => flt.FilterListId).OrderBy(flid => flid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class TagVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
return await _context.Tags
|
||||
.OrderBy(t => t.Id)
|
||||
.ProjectTo<TagVm>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TagVmProfile : Profile
|
||||
{
|
||||
public TagVmProfile()
|
||||
{
|
||||
CreateMap<Tag, TagVm>()
|
||||
.ForMember(t => t.FilterListIds,
|
||||
o => o.MapFrom(t =>
|
||||
t.FilterListTags.Select(flt => flt.FilterListId).OrderBy(flid => flid).AsEnumerable()));
|
||||
}
|
||||
}
|
||||
|
||||
public class TagVm
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public IEnumerable<int>? FilterListIds { get; init; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,25 +3,24 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Migrations.Tests
|
||||
namespace FilterLists.Directory.Infrastructure.Migrations.Tests;
|
||||
|
||||
public class SeedQueryDbContextTest
|
||||
{
|
||||
public class SeedQueryDbContextTest
|
||||
[Fact]
|
||||
public async Task Migrate_DoesNotThrowException()
|
||||
{
|
||||
[Fact]
|
||||
public async Task Migrate_DoesNotThrowException()
|
||||
var exception = await Record.ExceptionAsync(async () =>
|
||||
{
|
||||
var exception = await Record.ExceptionAsync(async () =>
|
||||
{
|
||||
var connString = Environment.GetEnvironmentVariable("ConnectionStrings__DirectoryConnection") ??
|
||||
throw new Exception();
|
||||
var options = new DbContextOptionsBuilder<QueryDbContext>()
|
||||
.UseNpgsql(connString, m => m.MigrationsAssembly(typeof(Initial).Assembly.GetName().Name))
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
await using var context = new QueryDbContext(options);
|
||||
await context.Database.MigrateAsync();
|
||||
});
|
||||
Assert.Null(exception);
|
||||
}
|
||||
var connString = Environment.GetEnvironmentVariable("ConnectionStrings__DirectoryConnection") ??
|
||||
throw new Exception();
|
||||
var options = new DbContextOptionsBuilder<QueryDbContext>()
|
||||
.UseNpgsql(connString, m => m.MigrationsAssembly(typeof(Initial).Assembly.GetName().Name))
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
await using var context = new QueryDbContext(options);
|
||||
await context.Database.MigrateAsync();
|
||||
});
|
||||
Assert.Null(exception);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,29 +6,28 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure
|
||||
namespace FilterLists.Directory.Infrastructure;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static IHostBuilder UseInfrastructure(this IHostBuilder hostBuilder)
|
||||
{
|
||||
public static IHostBuilder UseInfrastructure(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.UseLogging();
|
||||
}
|
||||
return hostBuilder.UseLogging();
|
||||
}
|
||||
|
||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSharedKernelLogging(configuration);
|
||||
services.AddDbContextPool<QueryDbContext>(o =>
|
||||
{
|
||||
services.AddSharedKernelLogging(configuration);
|
||||
services.AddDbContextPool<QueryDbContext>(o =>
|
||||
{
|
||||
o.UseNpgsql(configuration.GetConnectionString("DirectoryConnection"),
|
||||
po => po.MigrationsAssembly("FilterLists.Directory.Infrastructure.Migrations"));
|
||||
});
|
||||
services.AddScoped<IQueryContext, QueryContext>();
|
||||
}
|
||||
o.UseNpgsql(configuration.GetConnectionString("DirectoryConnection"),
|
||||
po => po.MigrationsAssembly("FilterLists.Directory.Infrastructure.Migrations"));
|
||||
});
|
||||
services.AddScoped<IQueryContext, QueryContext>();
|
||||
}
|
||||
|
||||
public static void UseInfrastructure(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseLogging();
|
||||
}
|
||||
public static void UseInfrastructure(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseLogging();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
public interface IQueryContext
|
||||
{
|
||||
public interface IQueryContext
|
||||
{
|
||||
IQueryable<FilterList> FilterLists { get; }
|
||||
IQueryable<Language> Languages { get; }
|
||||
IQueryable<License> Licenses { get; }
|
||||
IQueryable<Maintainer> Maintainers { get; }
|
||||
IQueryable<Software> Software { get; }
|
||||
IQueryable<Syntax> Syntaxes { get; }
|
||||
IQueryable<Tag> Tags { get; }
|
||||
}
|
||||
IQueryable<FilterList> FilterLists { get; }
|
||||
IQueryable<Language> Languages { get; }
|
||||
IQueryable<License> Licenses { get; }
|
||||
IQueryable<Maintainer> Maintainers { get; }
|
||||
IQueryable<Software> Software { get; }
|
||||
IQueryable<Syntax> Syntaxes { get; }
|
||||
IQueryable<Tag> Tags { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,27 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
internal class QueryContext : IQueryContext, IAsyncDisposable
|
||||
{
|
||||
internal class QueryContext : IQueryContext, IAsyncDisposable
|
||||
private readonly QueryDbContext _dbContext;
|
||||
|
||||
public QueryContext(QueryDbContext dbContext)
|
||||
{
|
||||
private readonly QueryDbContext _dbContext;
|
||||
|
||||
public QueryContext(QueryDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _dbContext.DisposeAsync();
|
||||
}
|
||||
|
||||
public IQueryable<FilterList> FilterLists => _dbContext.FilterLists.AsNoTracking();
|
||||
public IQueryable<Language> Languages => _dbContext.Languages.AsNoTracking();
|
||||
public IQueryable<License> Licenses => _dbContext.Licenses.AsNoTracking();
|
||||
public IQueryable<Maintainer> Maintainers => _dbContext.Maintainers.AsNoTracking();
|
||||
public IQueryable<Software> Software => _dbContext.Software.AsNoTracking();
|
||||
public IQueryable<Syntax> Syntaxes => _dbContext.Syntaxes.AsNoTracking();
|
||||
public IQueryable<Tag> Tags => _dbContext.Tags.AsNoTracking();
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _dbContext.DisposeAsync();
|
||||
}
|
||||
|
||||
public IQueryable<FilterList> FilterLists => _dbContext.FilterLists.AsNoTracking();
|
||||
public IQueryable<Language> Languages => _dbContext.Languages.AsNoTracking();
|
||||
public IQueryable<License> Licenses => _dbContext.Licenses.AsNoTracking();
|
||||
public IQueryable<Maintainer> Maintainers => _dbContext.Maintainers.AsNoTracking();
|
||||
public IQueryable<Software> Software => _dbContext.Software.AsNoTracking();
|
||||
public IQueryable<Syntax> Syntaxes => _dbContext.Syntaxes.AsNoTracking();
|
||||
public IQueryable<Tag> Tags => _dbContext.Tags.AsNoTracking();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,36 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
public class QueryDbContext : DbContext
|
||||
{
|
||||
public class QueryDbContext : DbContext
|
||||
public QueryDbContext(DbContextOptions<QueryDbContext> options) : base(options)
|
||||
{
|
||||
public QueryDbContext(DbContextOptions<QueryDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public DbSet<FilterList> FilterLists => Set<FilterList>();
|
||||
public DbSet<Language> Languages => Set<Language>();
|
||||
public DbSet<License> Licenses => Set<License>();
|
||||
public DbSet<Maintainer> Maintainers => Set<Maintainer>();
|
||||
public DbSet<Software> Software => Set<Software>();
|
||||
public DbSet<Syntax> Syntaxes => Set<Syntax>();
|
||||
public DbSet<Tag> Tags => Set<Tag>();
|
||||
public DbSet<FilterList> FilterLists => Set<FilterList>();
|
||||
public DbSet<Language> Languages => Set<Language>();
|
||||
public DbSet<License> Licenses => Set<License>();
|
||||
public DbSet<Maintainer> Maintainers => Set<Maintainer>();
|
||||
public DbSet<Software> Software => Set<Software>();
|
||||
public DbSet<Syntax> Syntaxes => Set<Syntax>();
|
||||
public DbSet<Tag> Tags => Set<Tag>();
|
||||
|
||||
public override int SaveChanges(bool acceptAllChangesOnSuccess)
|
||||
{
|
||||
throw new InvalidOperationException("This context is read-only.");
|
||||
}
|
||||
public override int SaveChanges(bool acceptAllChangesOnSuccess)
|
||||
{
|
||||
throw new InvalidOperationException("This context is read-only.");
|
||||
}
|
||||
|
||||
public override Task<int> SaveChangesAsync(
|
||||
bool acceptAllChangesOnSuccess,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new InvalidOperationException("This context is read-only.");
|
||||
}
|
||||
public override Task<int> SaveChangesAsync(
|
||||
bool acceptAllChangesOnSuccess,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new InvalidOperationException("This context is read-only.");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
|
||||
}
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Dependent
|
||||
{
|
||||
public int DependencyFilterListId { get; init; }
|
||||
public FilterList DependencyFilterList { get; } = null!;
|
||||
public int DependentFilterListId { get; init; }
|
||||
public FilterList DependentFilterList { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class DependentTypeConfiguration : IEntityTypeConfiguration<Dependent>
|
||||
public class Dependent
|
||||
{
|
||||
public int DependencyFilterListId { get; init; }
|
||||
public FilterList DependencyFilterList { get; } = null!;
|
||||
public int DependentFilterListId { get; init; }
|
||||
public FilterList DependentFilterList { get; } = null!;
|
||||
}
|
||||
|
||||
internal class DependentTypeConfiguration : IEntityTypeConfiguration<Dependent>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Dependent> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Dependent> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Dependent) + "s");
|
||||
builder.HasKey(d => new { d.DependencyFilterListId, d.DependentFilterListId });
|
||||
builder.HasOne(d => d.DependencyFilterList)
|
||||
.WithMany(fl => fl.DependentFilterLists)
|
||||
.HasForeignKey(d => d.DependencyFilterListId);
|
||||
builder.HasOne(d => d.DependentFilterList)
|
||||
.WithMany(fl => fl.DependencyFilterLists)
|
||||
.HasForeignKey(d => d.DependentFilterListId);
|
||||
builder.HasDataJsonFile<Dependent>();
|
||||
}
|
||||
builder.ToTable(nameof(Dependent) + "s");
|
||||
builder.HasKey(d => new { d.DependencyFilterListId, d.DependentFilterListId });
|
||||
builder.HasOne(d => d.DependencyFilterList)
|
||||
.WithMany(fl => fl.DependentFilterLists)
|
||||
.HasForeignKey(d => d.DependencyFilterListId);
|
||||
builder.HasOne(d => d.DependentFilterList)
|
||||
.WithMany(fl => fl.DependencyFilterLists)
|
||||
.HasForeignKey(d => d.DependentFilterListId);
|
||||
builder.HasDataJsonFile<Dependent>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,41 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterList
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public License? License { get; }
|
||||
public IReadOnlyCollection<FilterListSyntax> FilterListSyntaxes { get; } = new HashSet<FilterListSyntax>();
|
||||
public IReadOnlyCollection<FilterListLanguage> FilterListLanguages { get; } = new HashSet<FilterListLanguage>();
|
||||
public IReadOnlyCollection<FilterListTag> FilterListTags { get; } = new HashSet<FilterListTag>();
|
||||
public IReadOnlyCollection<FilterListViewUrl> ViewUrls { get; } = new HashSet<FilterListViewUrl>();
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? OnionUrl { get; init; }
|
||||
public Uri? PolicyUrl { get; init; }
|
||||
public Uri? SubmissionUrl { get; init; }
|
||||
public Uri? IssuesUrl { get; init; }
|
||||
public Uri? ForumUrl { get; init; }
|
||||
public Uri? ChatUrl { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public Uri? DonateUrl { get; init; }
|
||||
public IReadOnlyCollection<FilterListMaintainer> FilterListMaintainers { get; } = new HashSet<FilterListMaintainer>();
|
||||
public IReadOnlyCollection<Fork> UpstreamFilterLists { get; } = new HashSet<Fork>();
|
||||
public IReadOnlyCollection<Fork> ForkFilterLists { get; } = new HashSet<Fork>();
|
||||
public IReadOnlyCollection<Merge> IncludedInFilterLists { get; } = new HashSet<Merge>();
|
||||
public IReadOnlyCollection<Merge> IncludesFilterLists { get; } = new HashSet<Merge>();
|
||||
public IReadOnlyCollection<Dependent> DependencyFilterLists { get; } = new HashSet<Dependent>();
|
||||
public IReadOnlyCollection<Dependent> DependentFilterLists { get; } = new HashSet<Dependent>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListTypeConfiguration : IEntityTypeConfiguration<FilterList>
|
||||
public class FilterList
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public int? LicenseId { get; init; }
|
||||
public License? License { get; }
|
||||
public IReadOnlyCollection<FilterListSyntax> FilterListSyntaxes { get; } = new HashSet<FilterListSyntax>();
|
||||
public IReadOnlyCollection<FilterListLanguage> FilterListLanguages { get; } = new HashSet<FilterListLanguage>();
|
||||
public IReadOnlyCollection<FilterListTag> FilterListTags { get; } = new HashSet<FilterListTag>();
|
||||
public IReadOnlyCollection<FilterListViewUrl> ViewUrls { get; } = new HashSet<FilterListViewUrl>();
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? OnionUrl { get; init; }
|
||||
public Uri? PolicyUrl { get; init; }
|
||||
public Uri? SubmissionUrl { get; init; }
|
||||
public Uri? IssuesUrl { get; init; }
|
||||
public Uri? ForumUrl { get; init; }
|
||||
public Uri? ChatUrl { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public Uri? DonateUrl { get; init; }
|
||||
public IReadOnlyCollection<FilterListMaintainer> FilterListMaintainers { get; } = new HashSet<FilterListMaintainer>();
|
||||
public IReadOnlyCollection<Fork> UpstreamFilterLists { get; } = new HashSet<Fork>();
|
||||
public IReadOnlyCollection<Fork> ForkFilterLists { get; } = new HashSet<Fork>();
|
||||
public IReadOnlyCollection<Merge> IncludedInFilterLists { get; } = new HashSet<Merge>();
|
||||
public IReadOnlyCollection<Merge> IncludesFilterLists { get; } = new HashSet<Merge>();
|
||||
public IReadOnlyCollection<Dependent> DependencyFilterLists { get; } = new HashSet<Dependent>();
|
||||
public IReadOnlyCollection<Dependent> DependentFilterLists { get; } = new HashSet<Dependent>();
|
||||
}
|
||||
|
||||
internal class FilterListTypeConfiguration : IEntityTypeConfiguration<FilterList>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterList> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterList> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<FilterList>();
|
||||
}
|
||||
builder.HasDataJsonFile<FilterList>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterListLanguage
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public Language Language { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListLanguageTypeConfiguration : IEntityTypeConfiguration<FilterListLanguage>
|
||||
public class FilterListLanguage
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public Language Language { get; } = null!;
|
||||
}
|
||||
|
||||
internal class FilterListLanguageTypeConfiguration : IEntityTypeConfiguration<FilterListLanguage>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListLanguage> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListLanguage> builder)
|
||||
{
|
||||
builder.ToTable(nameof(FilterListLanguage) + "s");
|
||||
builder.HasKey(fll => new { fll.FilterListId, fll.Iso6391 });
|
||||
builder.HasDataJsonFile<FilterListLanguage>();
|
||||
}
|
||||
builder.ToTable(nameof(FilterListLanguage) + "s");
|
||||
builder.HasKey(fll => new { fll.FilterListId, fll.Iso6391 });
|
||||
builder.HasDataJsonFile<FilterListLanguage>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterListMaintainer
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int MaintainerId { get; init; }
|
||||
public Maintainer Maintainer { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListMaintainerTypeConfiguration : IEntityTypeConfiguration<FilterListMaintainer>
|
||||
public class FilterListMaintainer
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int MaintainerId { get; init; }
|
||||
public Maintainer Maintainer { get; } = null!;
|
||||
}
|
||||
|
||||
internal class FilterListMaintainerTypeConfiguration : IEntityTypeConfiguration<FilterListMaintainer>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListMaintainer> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListMaintainer> builder)
|
||||
{
|
||||
builder.ToTable(nameof(FilterListMaintainer) + "s");
|
||||
builder.HasKey(flm => new { flm.FilterListId, flm.MaintainerId });
|
||||
builder.HasDataJsonFile<FilterListMaintainer>();
|
||||
}
|
||||
builder.ToTable(nameof(FilterListMaintainer) + "s");
|
||||
builder.HasKey(flm => new { flm.FilterListId, flm.MaintainerId });
|
||||
builder.HasDataJsonFile<FilterListMaintainer>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterListSyntax
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int SyntaxId { get; init; }
|
||||
public Syntax Syntax { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListSyntaxTypeConfiguration : IEntityTypeConfiguration<FilterListSyntax>
|
||||
public class FilterListSyntax
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int SyntaxId { get; init; }
|
||||
public Syntax Syntax { get; } = null!;
|
||||
}
|
||||
|
||||
internal class FilterListSyntaxTypeConfiguration : IEntityTypeConfiguration<FilterListSyntax>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListSyntax> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListSyntax> builder)
|
||||
{
|
||||
builder.ToTable(nameof(FilterListSyntax) + "es");
|
||||
builder.HasKey(fls => new { fls.FilterListId, fls.SyntaxId });
|
||||
builder.HasDataJsonFile<FilterListSyntax>();
|
||||
}
|
||||
builder.ToTable(nameof(FilterListSyntax) + "es");
|
||||
builder.HasKey(fls => new { fls.FilterListId, fls.SyntaxId });
|
||||
builder.HasDataJsonFile<FilterListSyntax>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterListTag
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int TagId { get; init; }
|
||||
public Tag Tag { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListTagTypeConfiguration : IEntityTypeConfiguration<FilterListTag>
|
||||
public class FilterListTag
|
||||
{
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public int TagId { get; init; }
|
||||
public Tag Tag { get; } = null!;
|
||||
}
|
||||
|
||||
internal class FilterListTagTypeConfiguration : IEntityTypeConfiguration<FilterListTag>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListTag> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListTag> builder)
|
||||
{
|
||||
builder.ToTable(nameof(FilterListTag) + "s");
|
||||
builder.HasKey(flt => new { flt.FilterListId, flt.TagId });
|
||||
builder.HasDataJsonFile<FilterListTag>();
|
||||
}
|
||||
builder.ToTable(nameof(FilterListTag) + "s");
|
||||
builder.HasKey(flt => new { flt.FilterListId, flt.TagId });
|
||||
builder.HasDataJsonFile<FilterListTag>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class FilterListViewUrl
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public short SegmentNumber { get; init; }
|
||||
public short Primariness { get; init; }
|
||||
public Uri Url { get; init; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class FilterListViewUrlConfiguration : IEntityTypeConfiguration<FilterListViewUrl>
|
||||
public class FilterListViewUrl
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public int FilterListId { get; init; }
|
||||
public FilterList FilterList { get; } = null!;
|
||||
public short SegmentNumber { get; init; }
|
||||
public short Primariness { get; init; }
|
||||
public Uri Url { get; init; } = null!;
|
||||
}
|
||||
|
||||
internal class FilterListViewUrlConfiguration : IEntityTypeConfiguration<FilterListViewUrl>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListViewUrl> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterListViewUrl> builder)
|
||||
{
|
||||
builder.ToTable(nameof(FilterListViewUrl) + "s");
|
||||
builder.Property(u => u.SegmentNumber).HasDefaultValue(1);
|
||||
builder.Property(u => u.Primariness).HasDefaultValue(1);
|
||||
builder.HasIndex(u => new { u.FilterListId, u.SegmentNumber, u.Primariness }).IsUnique();
|
||||
builder.HasDataJsonFile<FilterListViewUrl>();
|
||||
}
|
||||
builder.ToTable(nameof(FilterListViewUrl) + "s");
|
||||
builder.Property(u => u.SegmentNumber).HasDefaultValue(1);
|
||||
builder.Property(u => u.Primariness).HasDefaultValue(1);
|
||||
builder.HasIndex(u => new { u.FilterListId, u.SegmentNumber, u.Primariness }).IsUnique();
|
||||
builder.HasDataJsonFile<FilterListViewUrl>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Fork
|
||||
{
|
||||
public int UpstreamFilterListId { get; init; }
|
||||
public FilterList UpstreamFilterList { get; } = null!;
|
||||
public int ForkFilterListId { get; init; }
|
||||
public FilterList ForkFilterList { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class ForkTypeConfiguration : IEntityTypeConfiguration<Fork>
|
||||
public class Fork
|
||||
{
|
||||
public int UpstreamFilterListId { get; init; }
|
||||
public FilterList UpstreamFilterList { get; } = null!;
|
||||
public int ForkFilterListId { get; init; }
|
||||
public FilterList ForkFilterList { get; } = null!;
|
||||
}
|
||||
|
||||
internal class ForkTypeConfiguration : IEntityTypeConfiguration<Fork>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Fork> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Fork> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Fork) + "s");
|
||||
builder.HasKey(f => new { f.UpstreamFilterListId, f.ForkFilterListId });
|
||||
builder.HasOne(f => f.UpstreamFilterList)
|
||||
.WithMany(fl => fl.ForkFilterLists)
|
||||
.HasForeignKey(f => f.UpstreamFilterListId);
|
||||
builder.HasOne(f => f.ForkFilterList)
|
||||
.WithMany(fl => fl.UpstreamFilterLists)
|
||||
.HasForeignKey(f => f.ForkFilterListId);
|
||||
builder.HasDataJsonFile<Fork>();
|
||||
}
|
||||
builder.ToTable(nameof(Fork) + "s");
|
||||
builder.HasKey(f => new { f.UpstreamFilterListId, f.ForkFilterListId });
|
||||
builder.HasOne(f => f.UpstreamFilterList)
|
||||
.WithMany(fl => fl.ForkFilterLists)
|
||||
.HasForeignKey(f => f.UpstreamFilterListId);
|
||||
builder.HasOne(f => f.ForkFilterList)
|
||||
.WithMany(fl => fl.UpstreamFilterLists)
|
||||
.HasForeignKey(f => f.ForkFilterListId);
|
||||
builder.HasDataJsonFile<Fork>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Language
|
||||
{
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public string Name { get; init; } = null!;
|
||||
public IReadOnlyCollection<FilterListLanguage> FilterListLanguages { get; } = new HashSet<FilterListLanguage>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class LanguageTypeConfiguration : IEntityTypeConfiguration<Language>
|
||||
public class Language
|
||||
{
|
||||
public string Iso6391 { get; init; } = null!;
|
||||
public string Name { get; init; } = null!;
|
||||
public IReadOnlyCollection<FilterListLanguage> FilterListLanguages { get; } = new HashSet<FilterListLanguage>();
|
||||
}
|
||||
|
||||
internal class LanguageTypeConfiguration : IEntityTypeConfiguration<Language>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Language> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Language> builder)
|
||||
{
|
||||
builder.HasKey(l => l.Iso6391);
|
||||
builder.Property(l => l.Iso6391)
|
||||
.IsFixedLength()
|
||||
.HasMaxLength(2);
|
||||
builder.HasDataJsonFile<Language>();
|
||||
}
|
||||
builder.HasKey(l => l.Iso6391);
|
||||
builder.Property(l => l.Iso6391)
|
||||
.IsFixedLength()
|
||||
.HasMaxLength(2);
|
||||
builder.HasDataJsonFile<Language>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class License
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public bool PermitsModification { get; init; }
|
||||
public bool PermitsDistribution { get; init; }
|
||||
public bool PermitsCommercialUse { get; init; }
|
||||
public IReadOnlyCollection<FilterList> FilterLists { get; } = new HashSet<FilterList>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class LicenseTypeConfiguration : IEntityTypeConfiguration<License>
|
||||
public class License
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public bool PermitsModification { get; init; }
|
||||
public bool PermitsDistribution { get; init; }
|
||||
public bool PermitsCommercialUse { get; init; }
|
||||
public IReadOnlyCollection<FilterList> FilterLists { get; } = new HashSet<FilterList>();
|
||||
}
|
||||
|
||||
internal class LicenseTypeConfiguration : IEntityTypeConfiguration<License>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<License> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<License> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<License>();
|
||||
}
|
||||
builder.HasDataJsonFile<License>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Maintainer
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public string? TwitterHandle { get; init; }
|
||||
public IReadOnlyCollection<FilterListMaintainer> FilterListMaintainers { get; } = new HashSet<FilterListMaintainer>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class MaintainerTypeConfiguration : IEntityTypeConfiguration<Maintainer>
|
||||
public class Maintainer
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public Uri? Url { get; init; }
|
||||
public string? EmailAddress { get; init; }
|
||||
public string? TwitterHandle { get; init; }
|
||||
public IReadOnlyCollection<FilterListMaintainer> FilterListMaintainers { get; } = new HashSet<FilterListMaintainer>();
|
||||
}
|
||||
|
||||
internal class MaintainerTypeConfiguration : IEntityTypeConfiguration<Maintainer>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Maintainer> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Maintainer> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<Maintainer>();
|
||||
}
|
||||
builder.HasDataJsonFile<Maintainer>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Merge
|
||||
{
|
||||
public int IncludedInFilterListId { get; init; }
|
||||
public FilterList IncludedInFilterList { get; } = null!;
|
||||
public int IncludesFilterListId { get; init; }
|
||||
public FilterList IncludesFilterList { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class MergeTypeConfiguration : IEntityTypeConfiguration<Merge>
|
||||
public class Merge
|
||||
{
|
||||
public int IncludedInFilterListId { get; init; }
|
||||
public FilterList IncludedInFilterList { get; } = null!;
|
||||
public int IncludesFilterListId { get; init; }
|
||||
public FilterList IncludesFilterList { get; } = null!;
|
||||
}
|
||||
|
||||
internal class MergeTypeConfiguration : IEntityTypeConfiguration<Merge>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Merge> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Merge> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Merge) + "s");
|
||||
builder.HasKey(m => new { m.IncludedInFilterListId, m.IncludesFilterListId });
|
||||
builder.HasOne(m => m.IncludedInFilterList)
|
||||
.WithMany(fl => fl.IncludesFilterLists)
|
||||
.HasForeignKey(m => m.IncludedInFilterListId);
|
||||
builder.HasOne(m => m.IncludesFilterList)
|
||||
.WithMany(fl => fl.IncludedInFilterLists)
|
||||
.HasForeignKey(m => m.IncludesFilterListId);
|
||||
builder.HasDataJsonFile<Merge>();
|
||||
}
|
||||
builder.ToTable(nameof(Merge) + "s");
|
||||
builder.HasKey(m => new { m.IncludedInFilterListId, m.IncludesFilterListId });
|
||||
builder.HasOne(m => m.IncludedInFilterList)
|
||||
.WithMany(fl => fl.IncludesFilterLists)
|
||||
.HasForeignKey(m => m.IncludedInFilterListId);
|
||||
builder.HasOne(m => m.IncludesFilterList)
|
||||
.WithMany(fl => fl.IncludedInFilterLists)
|
||||
.HasForeignKey(m => m.IncludesFilterListId);
|
||||
builder.HasDataJsonFile<Merge>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Software
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
public bool SupportsAbpUrlScheme { get; init; }
|
||||
public IReadOnlyCollection<SoftwareSyntax> SoftwareSyntaxes { get; } = new HashSet<SoftwareSyntax>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class SoftwareTypeConfiguration : IEntityTypeConfiguration<Software>
|
||||
public class Software
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? HomeUrl { get; init; }
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
public bool SupportsAbpUrlScheme { get; init; }
|
||||
public IReadOnlyCollection<SoftwareSyntax> SoftwareSyntaxes { get; } = new HashSet<SoftwareSyntax>();
|
||||
}
|
||||
|
||||
internal class SoftwareTypeConfiguration : IEntityTypeConfiguration<Software>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Software> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Software> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<Software>();
|
||||
}
|
||||
builder.HasDataJsonFile<Software>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class SoftwareSyntax
|
||||
{
|
||||
public int SoftwareId { get; init; }
|
||||
public Software Software { get; } = null!;
|
||||
public int SyntaxId { get; init; }
|
||||
public Syntax Syntax { get; } = null!;
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class SoftwareSyntaxTypeConfiguration : IEntityTypeConfiguration<SoftwareSyntax>
|
||||
public class SoftwareSyntax
|
||||
{
|
||||
public int SoftwareId { get; init; }
|
||||
public Software Software { get; } = null!;
|
||||
public int SyntaxId { get; init; }
|
||||
public Syntax Syntax { get; } = null!;
|
||||
}
|
||||
|
||||
internal class SoftwareSyntaxTypeConfiguration : IEntityTypeConfiguration<SoftwareSyntax>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<SoftwareSyntax> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<SoftwareSyntax> builder)
|
||||
{
|
||||
builder.ToTable(nameof(SoftwareSyntax) + "es");
|
||||
builder.HasKey(ss => new { ss.SoftwareId, ss.SyntaxId });
|
||||
builder.HasDataJsonFile<SoftwareSyntax>();
|
||||
}
|
||||
builder.ToTable(nameof(SoftwareSyntax) + "es");
|
||||
builder.HasKey(ss => new { ss.SoftwareId, ss.SyntaxId });
|
||||
builder.HasDataJsonFile<SoftwareSyntax>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Syntax
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? Url { get; init; }
|
||||
public IReadOnlyCollection<FilterListSyntax> FilterListSyntaxes { get; } = new HashSet<FilterListSyntax>();
|
||||
public IReadOnlyCollection<SoftwareSyntax> SoftwareSyntaxes { get; } = new HashSet<SoftwareSyntax>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class SyntaxTypeConfiguration : IEntityTypeConfiguration<Syntax>
|
||||
public class Syntax
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public Uri? Url { get; init; }
|
||||
public IReadOnlyCollection<FilterListSyntax> FilterListSyntaxes { get; } = new HashSet<FilterListSyntax>();
|
||||
public IReadOnlyCollection<SoftwareSyntax> SoftwareSyntaxes { get; } = new HashSet<SoftwareSyntax>();
|
||||
}
|
||||
|
||||
internal class SyntaxTypeConfiguration : IEntityTypeConfiguration<Syntax>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Syntax> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Syntax> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<Syntax>();
|
||||
}
|
||||
builder.HasDataJsonFile<Syntax>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities
|
||||
{
|
||||
public class Tag
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public IReadOnlyCollection<FilterListTag> FilterListTags { get; } = new HashSet<FilterListTag>();
|
||||
}
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
internal class TagTypeConfiguration : IEntityTypeConfiguration<Tag>
|
||||
public class Tag
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public string Name { get; init; } = null!;
|
||||
public string? Description { get; init; }
|
||||
public IReadOnlyCollection<FilterListTag> FilterListTags { get; } = new HashSet<FilterListTag>();
|
||||
}
|
||||
|
||||
internal class TagTypeConfiguration : IEntityTypeConfiguration<Tag>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Tag> builder)
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Tag> builder)
|
||||
{
|
||||
builder.HasDataJsonFile<Tag>();
|
||||
}
|
||||
builder.HasDataJsonFile<Tag>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,36 +5,35 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence;
|
||||
|
||||
public static class SeedExtension
|
||||
{
|
||||
public static class SeedExtension
|
||||
public static async Task MigrateAsync(this IHost host)
|
||||
{
|
||||
public static async Task MigrateAsync(this IHost host)
|
||||
{
|
||||
using var scope = host.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<QueryDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
using var scope = host.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<QueryDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SeedConfigurationExtension
|
||||
internal static class SeedConfigurationExtension
|
||||
{
|
||||
public static void HasDataJsonFile<TEntity>(this EntityTypeBuilder entityTypeBuilder)
|
||||
{
|
||||
public static void HasDataJsonFile<TEntity>(this EntityTypeBuilder entityTypeBuilder)
|
||||
var path = Path.Combine("../data", $"{typeof(TEntity).Name}.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var path = Path.Combine("../data", $"{typeof(TEntity).Name}.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var entitiesJson = File.ReadAllText(path);
|
||||
var entities = JsonSerializer.Deserialize<IEnumerable<TEntity>>(entitiesJson,
|
||||
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
var entitiesJson = File.ReadAllText(path);
|
||||
var entities = JsonSerializer.Deserialize<IEnumerable<TEntity>>(entitiesJson,
|
||||
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
|
||||
if (entities != null)
|
||||
{
|
||||
entityTypeBuilder.HasData((IEnumerable<object>)entities);
|
||||
}
|
||||
if (entities != null)
|
||||
{
|
||||
entityTypeBuilder.HasData((IEnumerable<object>)entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace FilterLists.SharedKernel.Logging
|
||||
namespace FilterLists.SharedKernel.Logging;
|
||||
|
||||
internal static class ConfigurationBuilder
|
||||
{
|
||||
internal static class ConfigurationBuilder
|
||||
{
|
||||
public static readonly LoggerConfiguration BaseLoggerConfiguration =
|
||||
new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console();
|
||||
}
|
||||
public static readonly LoggerConfiguration BaseLoggerConfiguration =
|
||||
new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,31 +8,30 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
|
||||
namespace FilterLists.SharedKernel.Logging
|
||||
namespace FilterLists.SharedKernel.Logging;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static class ConfigurationExtensions
|
||||
public static IHostBuilder UseLogging(this IHostBuilder hostBuilder)
|
||||
{
|
||||
public static IHostBuilder UseLogging(this IHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.UseSerilog();
|
||||
}
|
||||
return hostBuilder.UseSerilog();
|
||||
}
|
||||
|
||||
public static void AddSharedKernelLogging(this IServiceCollection services, IConfiguration configuration)
|
||||
public static void AddSharedKernelLogging(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
using var serverTelemetryChannel = new ServerTelemetryChannel
|
||||
{
|
||||
using var serverTelemetryChannel = new ServerTelemetryChannel
|
||||
{
|
||||
StorageFolder = configuration.GetSection(ApplicationInsightsOptions.Key)
|
||||
.Get<ApplicationInsightsOptions>()
|
||||
.ServerTelemetryChannelStoragePath
|
||||
};
|
||||
services.AddSingleton(typeof(ITelemetryChannel), serverTelemetryChannel);
|
||||
TelemetryDebugWriter.IsTracingDisabled = true;
|
||||
services.AddApplicationInsightsTelemetry();
|
||||
}
|
||||
StorageFolder = configuration.GetSection(ApplicationInsightsOptions.Key)
|
||||
.Get<ApplicationInsightsOptions>()
|
||||
.ServerTelemetryChannelStoragePath
|
||||
};
|
||||
services.AddSingleton(typeof(ITelemetryChannel), serverTelemetryChannel);
|
||||
TelemetryDebugWriter.IsTracingDisabled = true;
|
||||
services.AddApplicationInsightsTelemetry();
|
||||
}
|
||||
|
||||
public static void UseLogging(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseSerilogRequestLogging();
|
||||
}
|
||||
public static void UseLogging(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseSerilogRequestLogging();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,38 +3,37 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
|
||||
namespace FilterLists.SharedKernel.Logging
|
||||
namespace FilterLists.SharedKernel.Logging;
|
||||
|
||||
public static class HostRunner
|
||||
{
|
||||
public static class HostRunner
|
||||
public static async Task TryRunWithLoggingAsync(this IHost host, Func<Task>? runPreHostAsync = default)
|
||||
{
|
||||
public static async Task TryRunWithLoggingAsync(this IHost host, Func<Task>? runPreHostAsync = default)
|
||||
Log.Logger = ConfigurationBuilder.BaseLoggerConfiguration
|
||||
.WriteTo.ApplicationInsights(
|
||||
host.Services.GetRequiredService<TelemetryConfiguration>(),
|
||||
TelemetryConverter.Traces)
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Logger = ConfigurationBuilder.BaseLoggerConfiguration
|
||||
.WriteTo.ApplicationInsights(
|
||||
host.Services.GetRequiredService<TelemetryConfiguration>(),
|
||||
TelemetryConverter.Traces)
|
||||
.CreateLogger();
|
||||
if (runPreHostAsync != null)
|
||||
{
|
||||
Log.Information("Initializing pre-host");
|
||||
await runPreHostAsync();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (runPreHostAsync != null)
|
||||
{
|
||||
Log.Information("Initializing pre-host");
|
||||
await runPreHostAsync();
|
||||
}
|
||||
|
||||
Log.Information("Initializing host");
|
||||
await host.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
Log.Information("Initializing host");
|
||||
await host.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
namespace FilterLists.SharedKernel.Logging.Options
|
||||
{
|
||||
internal class ApplicationInsightsOptions
|
||||
{
|
||||
public const string Key = "ApplicationInsights";
|
||||
namespace FilterLists.SharedKernel.Logging.Options;
|
||||
|
||||
public string ServerTelemetryChannelStoragePath { get; init; } = null!;
|
||||
}
|
||||
internal class ApplicationInsightsOptions
|
||||
{
|
||||
public const string Key = "ApplicationInsights";
|
||||
|
||||
public string ServerTelemetryChannelStoragePath { get; init; } = null!;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue