refactor(dir): ♻️ use primary ctor syntax for responses

This commit is contained in:
Collin Barrett 2024-06-09 17:18:34 -05:00
parent d668655af1
commit 2cede3e539
10 changed files with 210 additions and 408 deletions

View file

@ -45,7 +45,7 @@ internal static void MapEndpoints(this WebApplication app)
OperationId = nameof(GetLists)
});
app.MapGet("/lists/{id:int}", async Task<Results<Ok<ListDetailsVm>, NotFound>>
app.MapGet("/lists/{id:int}", async Task<Results<Ok<Response>, NotFound>>
(int id, IMediator mediator, CancellationToken ct) =>
{
var list = await mediator.Send(new Request(id), ct);

View file

@ -33,6 +33,9 @@ internal static void AddOpenApiGen(this IServiceCollection services)
// include OpenApiTag Description and ExternalDocs
options.DocumentFilter<OpenApiTags.TagDescriptionsDocumentFilter>();
// allow re-using simple type names like "Response"
options.CustomSchemaIds(s => s.FullName?.Replace("+", "."));
});
}
}

View file

@ -8,59 +8,38 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetLanguages
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<LanguageVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(l => new Response(
l.Id,
l.Iso6391,
l.Name,
l.FilterListLanguages
.OrderBy(fl => fl.FilterListId)
.Select(fl => fl.FilterListId)
})
))
.TagWith(nameof(GetLanguages))
);
public sealed record Request : IStreamRequest<LanguageVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, LanguageVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<LanguageVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
public async IAsyncEnumerable<Response> Handle(Request request,
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var language in Query(ctx).WithCancellation(ct)) yield return language;
}
}
/// <param name="Id" example="37">The identifier.</param>
/// <param name="Iso6391" example="en">The unique ISO 639-1 code.</param>
/// <param name="Name" example="English">The unique ISO name.</param>
/// <param name="FilterListIds" example="[ 114, 141 ]">The identifiers of the FilterLists targeted by this Language.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(short Id, string Iso6391, string Name, IEnumerable<int> FilterListIds);
}

View file

@ -8,80 +8,50 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetLicenses
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<LicenseVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(l => new Response(
l.Id,
l.Name,
l.Url,
l.PermitsModification,
l.PermitsDistribution,
l.PermitsCommercialUse,
l.FilterLists
.OrderBy(f => f.Id)
.Select(f => f.Id)
})
))
.TagWith(nameof(GetLicenses))
);
public sealed record Request : IStreamRequest<LicenseVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, LicenseVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<LicenseVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
{
await foreach (var license in Query(ctx).WithCancellation(ct)) yield return license;
}
}
/// <param name="Id" example="5">The identifier.</param>
/// <param name="Name" example="All Rights Reserved">The unique name.</param>
/// <param name="Url" example="https://en.wikipedia.org/wiki/All_rights_reserved">The URL of the home page.</param>
/// <param name="PermitsModification" example="false">If the License permits modification.</param>
/// <param name="PermitsDistribution" example="false">If the License permits distribution.</param>
/// <param name="PermitsCommercialUse" example="false">If the License permits commercial use.</param>
/// <param name="FilterListIds" example="[ 6, 31 ]">The identifiers of the FilterLists released under this License.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(
int Id,
string Name,
Uri? Url,
bool PermitsModification,
bool PermitsDistribution,
bool PermitsCommercialUse,
IEnumerable<int> FilterListIds);
}

View file

@ -2,16 +2,15 @@
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 =
private static readonly Func<QueryDbContext, int, Task<Response?>> Query =
EF.CompileAsyncQuery((QueryDbContext ctx, int id) =>
ctx.FilterLists
.Select(f => new ListDetailsVm
.Select(f => new Response
{
Id = f.Id,
Name = f.Name,
@ -29,12 +28,12 @@ public static class GetListDetails
ViewUrls = f.ViewUrls
.OrderBy(u => u.SegmentNumber)
.ThenBy(u => u.Primariness)
.Select(u => new ViewUrlVm
{
SegmentNumber = u.SegmentNumber,
Primariness = u.Primariness,
Url = u.Url
}),
.Select(u => new ViewUrlResponse
(
u.SegmentNumber,
u.Primariness,
u.Url
)),
HomeUrl = f.HomeUrl,
OnionUrl = f.OnionUrl,
PolicyUrl = f.PolicyUrl,
@ -69,19 +68,20 @@ public static class GetListDetails
.TagWith(nameof(GetListDetails))
.SingleOrDefault(f => f.Id == id));
public sealed record Request(int Id) : IRequest<ListDetailsVm?>;
public sealed record Request(int Id) : IRequest<Response?>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IRequestHandler<Request, ListDetailsVm?>
private sealed class Handler(QueryDbContext ctx) : IRequestHandler<Request, Response?>
{
public Task<ListDetailsVm?> Handle(Request request, CancellationToken _)
public Task<Response?> Handle(Request request, CancellationToken _)
{
return Query(ctx, request.Id);
}
}
// TODO: refactor to primary ctor syntax while allowing EF Core query compilation to succeed
[PublicAPI]
public sealed record ListDetailsVm
public sealed record Response
{
/// <summary>
/// The identifier.
@ -132,7 +132,7 @@ public sealed record ListDetailsVm
/// <summary>
/// The view URLs.
/// </summary>
public IEnumerable<ViewUrlVm> ViewUrls { get; init; } = [];
public IEnumerable<ViewUrlResponse> ViewUrls { get; init; } = [];
/// <summary>
/// The URL of the home page.
@ -229,27 +229,14 @@ public sealed record ListDetailsVm
/// </summary>
/// <example>[]</example>
public IEnumerable<int> DependentFilterListIds { get; init; } = [];
[PublicAPI]
public sealed 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; }
}
}
/// <param name="SegmentNumber" example="1">The segment number of the URL for the FilterList (for multi-part lists).</param>
/// <param name="Primariness" example="1">
/// How primary the URL is for the FilterList segment (1 is original, 2+ is a mirror;
/// unique per SegmentNumber)
/// </param>
/// <param name="Url" example="https://easylist.to/easylist/easylist.txt">The view URL.</param>
[PublicAPI]
public sealed record ViewUrlResponse(short SegmentNumber, short Primariness, Uri Url);
}

View file

@ -8,107 +8,69 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetLists
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<ListVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(f => new Response
(
f.Id,
f.Name,
f.Description,
f.LicenseId,
f.FilterListSyntaxes
.OrderBy(fs => fs.SyntaxId)
.Select(fs => fs.SyntaxId),
LanguageIds = f.FilterListLanguages
f.FilterListLanguages
.OrderBy(fl => fl.LanguageId)
.Select(fl => fl.LanguageId),
TagIds = f.FilterListTags
f.FilterListTags
.OrderBy(ft => ft.TagId)
.Select(ft => ft.TagId),
PrimaryViewUrl = f.ViewUrls
f.ViewUrls
.OrderBy(u => u.SegmentNumber)
.ThenBy(u => u.Primariness)
.Select(u => u.Url)
.FirstOrDefault(),
MaintainerIds = f.FilterListMaintainers
f.FilterListMaintainers
.OrderBy(fm => fm.MaintainerId)
.Select(fm => fm.MaintainerId)
})
))
.TagWith(nameof(GetLists))
);
public sealed record Request : IStreamRequest<ListVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, ListVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<ListVm> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
{
await foreach (var list in Query(ctx).WithCancellation(ct)) yield return list;
}
}
/// <param name="Id" example="301">The identifier.</param>
/// <param name="Name" example="EasyList">The unique name in title case.</param>
/// <param name="Description"
/// example="EasyList is the primary filter list that removes most adverts from international web pages...">
/// The brief description in English (preferably quoted from the project).
/// </param>
/// <param name="LicenseId" example="4">The identifier of the License under which this FilterList is released.</param>
/// <param name="SyntaxIds" example="[ 3 ]">The identifiers of the Syntaxes implemented by this FilterList.</param>
/// <param name="LanguageIds" example="[ 37 ]">The identifiers of the Languages targeted by this FilterList.</param>
/// <param name="TagIds" example="[ 2 ]">The identifiers of the Tags applied to this FilterList.</param>
/// <param name="PrimaryViewUrl" example="https://easylist.to/easylist/easylist.txt">The primary view URL.</param>
/// <param name="MaintainerIds" example="[ 7 ]">The identifiers of the Maintainers of this FilterList.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(
int Id,
string Name,
string? Description,
int LicenseId,
IEnumerable<short> SyntaxIds,
IEnumerable<short> LanguageIds,
IEnumerable<int> TagIds,
Uri? PrimaryViewUrl,
IEnumerable<int> MaintainerIds);
}

View file

@ -8,74 +8,48 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetMaintainers
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<MaintainerVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(m => new Response(
m.Id,
m.Name,
m.Url,
m.EmailAddress,
m.TwitterHandle,
m.FilterListMaintainers
.OrderBy(fm => fm.FilterListId)
.Select(fm => fm.FilterListId)
})
))
.TagWith(nameof(GetMaintainers))
);
public sealed record Request : IStreamRequest<MaintainerVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, MaintainerVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<MaintainerVm> Handle(Request request,
public async IAsyncEnumerable<Response> Handle(Request request,
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var maintainer in Query(ctx).WithCancellation(ct)) yield return maintainer;
}
}
/// <param name="Id" example="7">The identifier.</param>
/// <param name="Name" example="The EasyList Authors">The unique name.</param>
/// <param name="Url" example="https://easylist.to/">The URL of the home page.</param>
/// <param name="EmailAddress" example="null">The email address.</param>
/// <param name="TwitterHandle" example="null">The Twitter handle.</param>
/// <param name="FilterListIds" example="[ 11, 13, 301 ]">The identifiers of the FilterLists maintained by this Maintainer.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(
int Id,
string Name,
Uri? Url,
string? EmailAddress,
string? TwitterHandle,
IEnumerable<int> FilterListIds);
}

View file

@ -8,85 +8,58 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetSoftware
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<SoftwareVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(s => new Response
(
s.Id,
s.Name,
s.Description,
s.HomeUrl,
s.DownloadUrl,
s.SupportsAbpUrlScheme,
s.SoftwareSyntaxes
.OrderBy(ss => ss.SyntaxId)
.Select(ss => ss.SyntaxId)
})
))
.TagWith(nameof(GetSoftware))
);
public sealed record Request : IStreamRequest<SoftwareVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, SoftwareVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<SoftwareVm> Handle(Request request,
public async IAsyncEnumerable<Response> Handle(Request request,
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var software in Query(ctx).WithCancellation(ct)) yield return software;
}
}
/// <param name="Id" example="2">The identifier.</param>
/// <param name="Name" example="Adblock Plus">The unique name.</param>
/// <param name="Description"
/// example="Adblock Plus is a free extension that allows you to customize your web experience...">
/// The description.
/// </param>
/// <param name="HomeUrl" example="https://adblockplus.org/">The URL of the home page.</param>
/// <param name="DownloadUrl" example="https://adblockplus.org/">The URL of the Software download.</param>
/// <param name="SupportsAbpUrlScheme" example="true">
/// If the Software supports the abp: URL scheme to click-to-subscribe to a FilterList.
/// </param>
/// <param name="SyntaxIds" example="[ 3, 28, 38, 48 ]">The identifiers of the Syntaxes that this Software supports.</param>
[PublicAPI]
public sealed 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. Its 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; } = [];
}
public sealed record Response(
long Id,
string Name,
string? Description,
Uri? HomeUrl,
Uri? DownloadUrl,
bool SupportsAbpUrlScheme,
IEnumerable<short> SyntaxIds);
}

View file

@ -8,76 +8,51 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetSyntaxes
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<SyntaxVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(s => new Response
(
s.Id,
s.Name,
s.Description,
s.Url,
s.FilterListSyntaxes
.OrderBy(fs => fs.FilterListId)
.Select(fs => fs.FilterListId),
SoftwareIds = s.SoftwareSyntaxes
s.SoftwareSyntaxes
.OrderBy(ss => ss.SoftwareId)
.Select(ss => ss.SoftwareId)
})
))
.TagWith(nameof(GetSyntaxes))
);
public sealed record Request : IStreamRequest<SyntaxVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, SyntaxVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<SyntaxVm> Handle(Request request,
public async IAsyncEnumerable<Response> Handle(Request request,
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var syntax in Query(ctx).WithCancellation(ct)) yield return syntax;
}
}
/// <param name="Id" example="3">The identifier.</param>
/// <param name="Name" example="Adblock Plus">The unique name.</param>
/// <param name="Description" example="null">The description.</param>
/// <param name="Url" example="https://adblockplus.org/filters">The URL of the home page.</param>
/// <param name="FilterListIds" example="[ 2, 6, 11 ]">The identifiers of the FilterLists implementing this Syntax.</param>
/// <param name="SoftwareIds" example="[ 1, 2, 3 ]">The identifiers of the Software that supports this Syntax.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(
short Id,
string Name,
string? Description,
Uri? Url,
IEnumerable<int> FilterListIds,
IEnumerable<short> SoftwareIds);
}

View file

@ -8,60 +8,39 @@ namespace FilterLists.Directory.Application.Queries;
public static class GetTags
{
private static readonly Func<QueryDbContext, IAsyncEnumerable<TagVm>> Query =
private static readonly Func<QueryDbContext, IAsyncEnumerable<Response>> 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
.Select(t => new Response
(
t.Id,
t.Name,
t.Description,
t.FilterListTags
.OrderBy(flt => flt.FilterListId)
.Select(flt => flt.FilterListId)
})
))
.TagWith(nameof(GetTags))
);
public sealed record Request : IStreamRequest<TagVm>;
public sealed record Request : IStreamRequest<Response>;
[UsedImplicitly]
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, TagVm>
private sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler<Request, Response>
{
public async IAsyncEnumerable<TagVm> Handle(Request request,
public async IAsyncEnumerable<Response> Handle(Request request,
[EnumeratorCancellation] CancellationToken ct)
{
await foreach (var tag in Query(ctx).WithCancellation(ct)) yield return tag;
}
}
/// <param name="Id" example="2">The identifier.</param>
/// <param name="Name" example="ads">The unique name.</param>
/// <param name="Description" example="Blocks advertisements">The description.</param>
/// <param name="FilterListIds" example="[ 1, 3, 6 ]">The identifiers of the FilterLists to which this Tag is applied.</param>
[PublicAPI]
public sealed 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; } = [];
}
public sealed record Response(int Id, string Name, string? Description, IEnumerable<int> FilterListIds);
}