mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
feat(dir): ✨ migrate query endpoints
This commit is contained in:
parent
8268dfbec8
commit
07343fa0d9
21 changed files with 964 additions and 90 deletions
56
services/Directory/FilterLists.Directory.Api/Endpoints.cs
Normal file
56
services/Directory/FilterLists.Directory.Api/Endpoints.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using FilterLists.Directory.Application.Queries;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using static FilterLists.Directory.Application.Queries.GetListDetails;
|
||||
|
||||
namespace FilterLists.Directory.Api;
|
||||
|
||||
internal static class Endpoints
|
||||
{
|
||||
internal static void MapEndpoints(this WebApplication app)
|
||||
{
|
||||
app.MapGet("/languages",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetLanguages.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/licenses",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetLicenses.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/lists",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetLists.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/lists/{id:int}", async Task<Results<Ok<ListDetailsVm>, NotFound>>
|
||||
(int id, IMediator mediator, CancellationToken ct) =>
|
||||
{
|
||||
var list = await mediator.Send(new Request(id), ct);
|
||||
return list is not null
|
||||
? TypedResults.Ok(list)
|
||||
: TypedResults.NotFound();
|
||||
});
|
||||
|
||||
app.MapGet("/maintainers",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetMaintainers.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/software",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetSoftware.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/syntaxes",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetSyntaxes.Request(), ct)
|
||||
);
|
||||
|
||||
app.MapGet("/tags",
|
||||
(IMediator mediator, CancellationToken ct) =>
|
||||
mediator.CreateStream(new GetTags.Request(), ct)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\FilterLists.ServiceDefaults\FilterLists.ServiceDefaults.csproj"/>
|
||||
<ProjectReference Include="..\FilterLists.Directory.Application\FilterLists.Directory.Application.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,41 +1,17 @@
|
|||
using FilterLists.Directory.Api;
|
||||
using FilterLists.Directory.Application;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.WebHost.ConfigureKestrel(serverOptions => serverOptions.AddServerHeader = false);
|
||||
|
||||
// Add service defaults & Aspire components.
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.AddApplication();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
app.UseExceptionHandler();
|
||||
|
||||
var summaries = new[]
|
||||
{
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
};
|
||||
|
||||
app.MapGet("/weatherforecast", () =>
|
||||
{
|
||||
var forecast = Enumerable.Range(1, 5).Select(index =>
|
||||
new WeatherForecast
|
||||
(
|
||||
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||
Random.Shared.Next(-20, 55),
|
||||
summaries[Random.Shared.Next(summaries.Length)]
|
||||
))
|
||||
.ToArray();
|
||||
return forecast;
|
||||
});
|
||||
|
||||
app.MapEndpoints();
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
app.Run();
|
||||
|
||||
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
|
||||
{
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
app.Run();
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"launchUrl": "lists",
|
||||
"applicationUrl": "http://localhost:5444",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
|
|
@ -15,11 +15,11 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "weatherforecast",
|
||||
"launchUrl": "lists",
|
||||
"applicationUrl": "https://localhost:7490;http://localhost:5444",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using FilterLists.Directory.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace FilterLists.Directory.Application;
|
||||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static void AddApplication(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.AddInfrastructure();
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
{
|
||||
cfg.RegisterServicesFromAssembly(typeof(ConfigurationExtensions).Assembly);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
||||
<PackageReference Include="MediatR" Version="12.2.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FilterLists.Directory.Infrastructure\FilterLists.Directory.Infrastructure.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLanguages
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<LanguageVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Languages
|
||||
.Where(l => l.FilterListLanguages.Any())
|
||||
.OrderBy(l => l.Iso6391)
|
||||
.Select(l => new LanguageVm
|
||||
{
|
||||
Id = l.Id,
|
||||
Iso6391 = l.Iso6391,
|
||||
Name = l.Name,
|
||||
FilterListIds = l.FilterListLanguages
|
||||
.OrderBy(fl => fl.FilterListId)
|
||||
.Select(fl => fl.FilterListId)
|
||||
})
|
||||
.TagWith(nameof(GetLanguages))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<LanguageVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, LanguageVm>
|
||||
{
|
||||
public async IAsyncEnumerable<LanguageVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var language in Query(ctx).WithCancellation(ct)) yield return language;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record LanguageVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>37</example>
|
||||
public short Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique ISO 639-1 code.
|
||||
/// </summary>
|
||||
/// <example>en</example>
|
||||
public required string Iso6391 { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique ISO name.
|
||||
/// </summary>
|
||||
/// <example>English</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists targeted by this Language.
|
||||
/// </summary>
|
||||
/// <example>[ 114, 141 ]</example>
|
||||
public IEnumerable<int> FilterListIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLicenses
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<LicenseVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Licenses
|
||||
.Where(l => l.FilterLists.Any())
|
||||
.OrderBy(l => l.Id)
|
||||
.Select(l => new LicenseVm
|
||||
{
|
||||
Id = l.Id,
|
||||
Name = l.Name,
|
||||
Url = l.Url,
|
||||
PermitsModification = l.PermitsModification,
|
||||
PermitsDistribution = l.PermitsDistribution,
|
||||
PermitsCommercialUse = l.PermitsCommercialUse,
|
||||
FilterListIds = l.FilterLists
|
||||
.OrderBy(f => f.Id)
|
||||
.Select(f => f.Id)
|
||||
})
|
||||
.TagWith(nameof(GetLicenses))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<LicenseVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, LicenseVm>
|
||||
{
|
||||
public async IAsyncEnumerable<LicenseVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var license in Query(ctx).WithCancellation(ct)) yield return license;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record LicenseVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>5</example>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name.
|
||||
/// </summary>
|
||||
/// <example>All Rights Reserved</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the home page.
|
||||
/// </summary>
|
||||
/// <example>https://en.wikipedia.org/wiki/All_rights_reserved</example>
|
||||
public Uri? Url { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// If the License permits modification.
|
||||
/// </summary>
|
||||
/// <example>false</example>
|
||||
public bool PermitsModification { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// If the License permits distribution.
|
||||
/// </summary>
|
||||
/// <example>false</example>
|
||||
public bool PermitsDistribution { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// If the License permits commercial use.
|
||||
/// </summary>
|
||||
/// <example>false</example>
|
||||
public bool PermitsCommercialUse { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists released under this License.
|
||||
/// </summary>
|
||||
/// <example>[ 6, 31 ]</example>
|
||||
public IEnumerable<int> FilterListIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using static FilterLists.Directory.Application.Queries.GetListDetails.ListDetailsVm;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetListDetails
|
||||
{
|
||||
private static readonly Func<QueryDbContext, int, Task<ListDetailsVm?>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx, int id) =>
|
||||
ctx.FilterLists
|
||||
.Select(f => new ListDetailsVm
|
||||
{
|
||||
Id = f.Id,
|
||||
Name = f.Name,
|
||||
Description = f.Description,
|
||||
LicenseId = f.LicenseId,
|
||||
SyntaxIds = f.FilterListSyntaxes
|
||||
.OrderBy(fs => fs.SyntaxId)
|
||||
.Select(fs => fs.SyntaxId),
|
||||
LanguageIds = f.FilterListLanguages
|
||||
.OrderBy(fl => fl.LanguageId)
|
||||
.Select(fl => fl.LanguageId),
|
||||
TagIds = f.FilterListTags
|
||||
.OrderBy(ft => ft.TagId)
|
||||
.Select(ft => ft.TagId),
|
||||
ViewUrls = f.ViewUrls
|
||||
.OrderBy(u => u.SegmentNumber)
|
||||
.ThenBy(u => u.Primariness)
|
||||
.Select(u => new ViewUrlVm
|
||||
{
|
||||
SegmentNumber = u.SegmentNumber,
|
||||
Primariness = u.Primariness,
|
||||
Url = u.Url
|
||||
}),
|
||||
HomeUrl = f.HomeUrl,
|
||||
OnionUrl = f.OnionUrl,
|
||||
PolicyUrl = f.PolicyUrl,
|
||||
SubmissionUrl = f.SubmissionUrl,
|
||||
IssuesUrl = f.IssuesUrl,
|
||||
ForumUrl = f.ForumUrl,
|
||||
ChatUrl = f.ChatUrl,
|
||||
EmailAddress = f.EmailAddress,
|
||||
DonateUrl = f.DonateUrl,
|
||||
MaintainerIds = f.FilterListMaintainers
|
||||
.OrderBy(fm => fm.MaintainerId)
|
||||
.Select(fm => fm.MaintainerId),
|
||||
UpstreamFilterListIds = f.UpstreamFilterLists
|
||||
.OrderBy(ff => ff.UpstreamFilterListId)
|
||||
.Select(ff => ff.UpstreamFilterListId),
|
||||
ForkFilterListIds = f.ForkFilterLists
|
||||
.OrderBy(ff => ff.ForkFilterListId)
|
||||
.Select(ff => ff.ForkFilterListId),
|
||||
IncludedInFilterListIds = f.IncludedInFilterLists
|
||||
.OrderBy(fm => fm.IncludedInFilterListId)
|
||||
.Select(fm => fm.IncludedInFilterListId),
|
||||
IncludesFilterListIds = f.IncludesFilterLists
|
||||
.OrderBy(fm => fm.IncludesFilterListId)
|
||||
.Select(fm => fm.IncludesFilterListId),
|
||||
DependencyFilterListIds = f.DependencyFilterLists
|
||||
.OrderBy(fd => fd.DependencyFilterListId)
|
||||
.Select(fd => fd.DependencyFilterListId),
|
||||
DependentFilterListIds = f.DependentFilterLists
|
||||
.OrderBy(fd => fd.DependentFilterListId)
|
||||
.Select(fd => fd.DependentFilterListId)
|
||||
})
|
||||
.TagWith(nameof(GetListDetails))
|
||||
.SingleOrDefault(f => f.Id == id));
|
||||
|
||||
public sealed record Request(int Id) : IRequest<ListDetailsVm?>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IRequestHandler<Request, ListDetailsVm?>
|
||||
{
|
||||
public Task<ListDetailsVm?> Handle(Request request, CancellationToken _)
|
||||
{
|
||||
return Query(ctx, request.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record ListDetailsVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>301</example>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name in title case.
|
||||
/// </summary>
|
||||
/// <example>EasyList</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The brief description in English (preferably quoted from the project).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// EasyList is the primary filter list that removes most adverts from international web pages, including unwanted
|
||||
/// frames, images, and objects. It is the most popular list used by many ad blockers and forms the basis of over a
|
||||
/// dozen combination and supplementary filter lists.
|
||||
/// </example>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of the License under which this FilterList is released.
|
||||
/// </summary>
|
||||
/// <example>4</example>
|
||||
public int LicenseId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Syntaxes implemented by this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 3 ]</example>
|
||||
public IEnumerable<short> SyntaxIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Languages targeted by this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 37 ]</example>
|
||||
public IEnumerable<short> LanguageIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Tags applied to this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 2 ]</example>
|
||||
public IEnumerable<int> TagIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The view URLs.
|
||||
/// </summary>
|
||||
public IEnumerable<ViewUrlVm> ViewUrls { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the home page.
|
||||
/// </summary>
|
||||
/// <example>https://easylist.to/</example>
|
||||
public Uri? HomeUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the Tor / Onion page.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public Uri? OnionUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the policy/guidelines for the types of rules this FilterList includes.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public Uri? PolicyUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the submission/contact form for adding rules to this FilterList.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public Uri? SubmissionUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the GitHub Issues page.
|
||||
/// </summary>
|
||||
/// <example>https://github.com/easylist/easylist/issues</example>
|
||||
public Uri? IssuesUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the forum page.
|
||||
/// </summary>
|
||||
/// <example>https://forums.lanik.us/viewforum.php?f=23</example>
|
||||
public Uri? ForumUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the chat room.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public Uri? ChatUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The email address at which the project can be contacted.
|
||||
/// </summary>
|
||||
/// <example>easylist@protonmail.com</example>
|
||||
public string? EmailAddress { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL at which donations to the project can be made.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public Uri? DonateUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Maintainers of this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 7 ]</example>
|
||||
public IEnumerable<int> MaintainerIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists from which this FilterList was forked.
|
||||
/// </summary>
|
||||
/// <example>[]</example>
|
||||
public IEnumerable<int> UpstreamFilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists that have been forked from this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 166, 565 ]</example>
|
||||
public IEnumerable<int> ForkFilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists that include this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[]</example>
|
||||
public IEnumerable<int> IncludedInFilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists that this FilterList includes.
|
||||
/// </summary>
|
||||
/// <example>[ 11, 13, 168 ]</example>
|
||||
public IEnumerable<int> IncludesFilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists that this FilterList depends upon.
|
||||
/// </summary>
|
||||
/// <example>[]</example>
|
||||
public IEnumerable<int> DependencyFilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists dependent upon this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[]</example>
|
||||
public IEnumerable<int> DependentFilterListIds { get; init; } = [];
|
||||
|
||||
[PublicAPI]
|
||||
public record ViewUrlVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The segment number of the URL for the FilterList (for multi-part lists).
|
||||
/// </summary>
|
||||
/// <example>1</example>
|
||||
public short SegmentNumber { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// How primary the URL is for the FilterList segment (1 is original, 2+ is a mirror; unique per SegmentNumber)
|
||||
/// </summary>
|
||||
/// <example>1</example>
|
||||
public short Primariness { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The view URL.
|
||||
/// </summary>
|
||||
/// <example>https://easylist.to/easylist/easylist.txt</example>
|
||||
public required Uri Url { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetLists
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<ListVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.FilterLists
|
||||
.OrderBy(f => f.Id)
|
||||
.Select(f => new ListVm
|
||||
{
|
||||
Id = f.Id,
|
||||
Name = f.Name,
|
||||
Description = f.Description,
|
||||
LicenseId = f.LicenseId,
|
||||
SyntaxIds = f.FilterListSyntaxes
|
||||
.OrderBy(fs => fs.SyntaxId)
|
||||
.Select(fs => fs.SyntaxId),
|
||||
LanguageIds = f.FilterListLanguages
|
||||
.OrderBy(fl => fl.LanguageId)
|
||||
.Select(fl => fl.LanguageId),
|
||||
TagIds = f.FilterListTags
|
||||
.OrderBy(ft => ft.TagId)
|
||||
.Select(ft => ft.TagId),
|
||||
PrimaryViewUrl = f.ViewUrls
|
||||
.OrderBy(u => u.SegmentNumber)
|
||||
.ThenBy(u => u.Primariness)
|
||||
.Select(u => u.Url)
|
||||
.FirstOrDefault(),
|
||||
MaintainerIds = f.FilterListMaintainers
|
||||
.OrderBy(fm => fm.MaintainerId)
|
||||
.Select(fm => fm.MaintainerId)
|
||||
})
|
||||
.TagWith(nameof(GetLists))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<ListVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, ListVm>
|
||||
{
|
||||
public async IAsyncEnumerable<ListVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var list in Query(ctx).WithCancellation(ct)) yield return list;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record ListVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>301</example>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name in title case.
|
||||
/// </summary>
|
||||
/// <example>EasyList</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The brief description in English (preferably quoted from the project).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// EasyList is the primary filter list that removes most adverts from international web pages, including unwanted
|
||||
/// frames, images, and objects. It is the most popular list used by many ad blockers and forms the basis of over a
|
||||
/// dozen combination and supplementary filter lists.
|
||||
/// </example>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of the License under which this FilterList is released.
|
||||
/// </summary>
|
||||
/// <example>4</example>
|
||||
public int LicenseId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Syntaxes implemented by this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 3 ]</example>
|
||||
public IEnumerable<short> SyntaxIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Languages targeted by this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 37 ]</example>
|
||||
public IEnumerable<short> LanguageIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Tags applied to this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 2 ]</example>
|
||||
public IEnumerable<int> TagIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The primary view URL.
|
||||
/// </summary>
|
||||
/// <example>https://easylist.to/easylist/easylist.txt</example>
|
||||
public Uri? PrimaryViewUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Maintainers of this FilterList.
|
||||
/// </summary>
|
||||
/// <example>[ 7 ]</example>
|
||||
public IEnumerable<int> MaintainerIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetMaintainers
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<MaintainerVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Maintainers
|
||||
.Where(m => m.FilterListMaintainers.Any())
|
||||
.OrderBy(m => m.Id)
|
||||
.Select(m => new MaintainerVm
|
||||
{
|
||||
Id = m.Id,
|
||||
Name = m.Name,
|
||||
Url = m.Url,
|
||||
EmailAddress = m.EmailAddress,
|
||||
TwitterHandle = m.TwitterHandle,
|
||||
FilterListIds = m.FilterListMaintainers
|
||||
.OrderBy(fm => fm.FilterListId)
|
||||
.Select(fm => fm.FilterListId)
|
||||
})
|
||||
.TagWith(nameof(GetMaintainers))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<MaintainerVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, MaintainerVm>
|
||||
{
|
||||
public async IAsyncEnumerable<MaintainerVm> Handle(Request request,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var maintainer in Query(ctx).WithCancellation(ct)) yield return maintainer;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record MaintainerVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>7</example>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name.
|
||||
/// </summary>
|
||||
/// <example>The EasyList Authors</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the home page.
|
||||
/// </summary>
|
||||
/// <example>https://easylist.to/</example>
|
||||
public Uri? Url { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The email address.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public string? EmailAddress { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The Twitter handle.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public string? TwitterHandle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists maintained by this Maintainer.
|
||||
/// </summary>
|
||||
/// <example>[ 11, 13, 301 ]</example>
|
||||
public IEnumerable<int> FilterListIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetSoftware
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<SoftwareVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Software
|
||||
.Where(s => s.SoftwareSyntaxes
|
||||
.Any(ss => ss.Syntax.FilterListSyntaxes.Any()))
|
||||
.OrderBy(s => s.Id)
|
||||
.Select(s => new SoftwareVm
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = s.Name,
|
||||
Description = s.Description,
|
||||
HomeUrl = s.HomeUrl,
|
||||
DownloadUrl = s.DownloadUrl,
|
||||
SupportsAbpUrlScheme = s.SupportsAbpUrlScheme,
|
||||
SyntaxIds = s.SoftwareSyntaxes
|
||||
.OrderBy(ss => ss.SyntaxId)
|
||||
.Select(ss => ss.SyntaxId)
|
||||
})
|
||||
.TagWith(nameof(GetSoftware))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<SoftwareVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, SoftwareVm>
|
||||
{
|
||||
public async IAsyncEnumerable<SoftwareVm> Handle(Request request,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var software in Query(ctx).WithCancellation(ct)) yield return software;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record SoftwareVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>2</example>
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name.
|
||||
/// </summary>
|
||||
/// <example>Adblock Plus</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The description.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Adblock Plus is a free extension that allows you to customize your web experience. You can block annoying ads,
|
||||
/// disable tracking and lots more. It’s available for all major desktop browsers and for your mobile devices.
|
||||
/// </example>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the home page.
|
||||
/// </summary>
|
||||
/// <example>https://adblockplus.org/</example>
|
||||
public Uri? HomeUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the Software download.
|
||||
/// </summary>
|
||||
/// <example>https://adblockplus.org/</example>
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// If the Software supports the abp: URL scheme to click-to-subscribe to a FilterList.
|
||||
/// </summary>
|
||||
/// <example>true</example>
|
||||
public bool SupportsAbpUrlScheme { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Syntaxes that this Software supports.
|
||||
/// </summary>
|
||||
/// <example>[ 3, 28, 38, 48 ]</example>
|
||||
public IEnumerable<short> SyntaxIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetSyntaxes
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<SyntaxVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Syntaxes
|
||||
.Where(s => s.FilterListSyntaxes.Any())
|
||||
.OrderBy(s => s.Id)
|
||||
.Select(s => new SyntaxVm
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = s.Name,
|
||||
Description = s.Description,
|
||||
Url = s.Url,
|
||||
FilterListIds = s.FilterListSyntaxes
|
||||
.OrderBy(fs => fs.FilterListId)
|
||||
.Select(fs => fs.FilterListId),
|
||||
SoftwareIds = s.SoftwareSyntaxes
|
||||
.OrderBy(ss => ss.SoftwareId)
|
||||
.Select(ss => ss.SoftwareId)
|
||||
})
|
||||
.TagWith(nameof(GetSyntaxes))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<SyntaxVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, SyntaxVm>
|
||||
{
|
||||
public async IAsyncEnumerable<SyntaxVm> Handle(Request request,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var syntax in Query(ctx).WithCancellation(ct)) yield return syntax;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record SyntaxVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>3</example>
|
||||
public short Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name.
|
||||
/// </summary>
|
||||
/// <example>Adblock Plus</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The description.
|
||||
/// </summary>
|
||||
/// <example>null</example>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the home page.
|
||||
/// </summary>
|
||||
/// <example>https://adblockplus.org/filters</example>
|
||||
public Uri? Url { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists implementing this Syntax.
|
||||
/// </summary>
|
||||
/// <example>[ 2, 6, 11 ]</example>
|
||||
public IEnumerable<int> FilterListIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the Software that supports this Syntax.
|
||||
/// </summary>
|
||||
/// <example>[ 1, 2, 3 ]</example>
|
||||
public IEnumerable<short> SoftwareIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
using JetBrains.Annotations;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Application.Queries;
|
||||
|
||||
public static class GetTags
|
||||
{
|
||||
private static readonly Func<QueryDbContext, IAsyncEnumerable<TagVm>> Query =
|
||||
EF.CompileAsyncQuery((QueryDbContext ctx) =>
|
||||
ctx.Tags
|
||||
.Where(t => t.FilterListTags.Any())
|
||||
.OrderBy(t => t.Id)
|
||||
.Select(t => new TagVm
|
||||
{
|
||||
Id = t.Id,
|
||||
Name = t.Name,
|
||||
Description = t.Description,
|
||||
FilterListIds = t.FilterListTags
|
||||
.OrderBy(flt => flt.FilterListId)
|
||||
.Select(flt => flt.FilterListId)
|
||||
})
|
||||
.TagWith(nameof(GetTags))
|
||||
);
|
||||
|
||||
public sealed record Request : IStreamRequest<TagVm>;
|
||||
|
||||
[UsedImplicitly]
|
||||
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, TagVm>
|
||||
{
|
||||
public async IAsyncEnumerable<TagVm> Handle(Request request,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
await foreach (var tag in Query(ctx).WithCancellation(ct)) yield return tag;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record TagVm
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier.
|
||||
/// </summary>
|
||||
/// <example>2</example>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique name.
|
||||
/// </summary>
|
||||
/// <example>ads</example>
|
||||
public required string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The description.
|
||||
/// </summary>
|
||||
/// <example>Blocks advertisements</example>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The identifiers of the FilterLists to which this Tag is applied.
|
||||
/// </summary>
|
||||
/// <example>[ 1, 3, 6 ]</example>
|
||||
public IEnumerable<int> FilterListIds { get; init; } = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
using FilterLists.Directory.Infrastructure.MigrationService;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
builder.AddInfrastructureForDbMaintenance();
|
||||
builder.AddInfrastructure();
|
||||
builder.Services.AddHostedService<Worker>();
|
||||
|
||||
var host = builder.Build();
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ namespace FilterLists.Directory.Infrastructure;
|
|||
|
||||
public static class ConfigurationExtensions
|
||||
{
|
||||
public static void AddInfrastructureForDbMaintenance(this IHostApplicationBuilder builder)
|
||||
public static void AddInfrastructure(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.AddSqlServerDbContext<QueryDbContext>("directorydb",
|
||||
settings => { },
|
||||
o => o.UseSqlServer(so => so.MigrationsAssembly("FilterLists.Directory.Infrastructure.Migrations"))
|
||||
);
|
||||
_ => { },
|
||||
o =>
|
||||
{
|
||||
o.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
o.UseSqlServer(so => so.MigrationsAssembly("FilterLists.Directory.Infrastructure.Migrations"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
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; }
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
internal sealed class QueryContext(QueryDbContext dbContext) : IQueryContext, IAsyncDisposable, IDisposable
|
||||
{
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
return dbContext.DisposeAsync();
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
dbContext.Dispose();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
|
||||
|
||||
// TODO: explicitly make more readonly-ish
|
||||
public class QueryDbContext(DbContextOptions<QueryDbContext> options) : DbContext(options)
|
||||
{
|
||||
public DbSet<FilterList> FilterLists => Set<FilterList>();
|
||||
|
|
@ -13,18 +14,6 @@ public class QueryDbContext(DbContextOptions<QueryDbContext> options) : DbContex
|
|||
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 Task<int> SaveChangesAsync(
|
||||
bool acceptAllChangesOnSuccess,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
throw new InvalidOperationException("This context is read-only.");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.UseCollation("Latin1_General_100_CI_AS_SC");
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
builder.AddProject<FilterLists_Directory_Api>("directoryapi")
|
||||
.WithReference(directoryDb)
|
||||
.WithReference(appInsights);
|
||||
.WithReference(appInsights)
|
||||
.WithExternalHttpEndpoints();
|
||||
|
||||
builder.Build().Run();
|
||||
|
|
@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Infra
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Infrastructure", "Directory\FilterLists.Directory.Infrastructure\FilterLists.Directory.Infrastructure.csproj", "{02B4FF36-8012-42CD-BE3E-92475C091A4C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilterLists.Directory.Application", "Directory\FilterLists.Directory.Application\FilterLists.Directory.Application.csproj", "{0D4420C3-283B-4C0A-88B4-D39E2033B80F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -52,6 +54,10 @@ Global
|
|||
{02B4FF36-8012-42CD-BE3E-92475C091A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{02B4FF36-8012-42CD-BE3E-92475C091A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{02B4FF36-8012-42CD-BE3E-92475C091A4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0D4420C3-283B-4C0A-88B4-D39E2033B80F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -64,5 +70,6 @@ Global
|
|||
{5DD434A8-D394-4178-9DF3-669EC85EA229} = {C4F6197D-9546-480E-AE92-1FC264D8E88F}
|
||||
{37170729-4BC9-412F-9ADF-97592A677E90} = {C4F6197D-9546-480E-AE92-1FC264D8E88F}
|
||||
{02B4FF36-8012-42CD-BE3E-92475C091A4C} = {C4F6197D-9546-480E-AE92-1FC264D8E88F}
|
||||
{0D4420C3-283B-4C0A-88B4-D39E2033B80F} = {C4F6197D-9546-480E-AE92-1FC264D8E88F}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
Loading…
Reference in a new issue