diff --git a/services/Directory/FilterLists.Directory.Api/Endpoints.cs b/services/Directory/FilterLists.Directory.Api/Endpoints.cs
new file mode 100644
index 0000000000..fb05def77b
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Api/Endpoints.cs
@@ -0,0 +1,22 @@
+using FilterLists.Directory.Application.Queries;
+using MediatR;
+
+namespace FilterLists.Directory.Api;
+
+internal static class Endpoints
+{
+ internal static void MapEndpoints(this WebApplication app)
+ {
+ var lists = app.MapGroup("/lists");
+
+ lists.MapGet("/",
+ (IMediator mediator, CancellationToken ct) =>
+ mediator.CreateStream(new GetLists.Request(), ct)
+ );
+
+ lists.MapGet("/{id:int}",
+ async (int id, IMediator mediator, CancellationToken ct) =>
+ await mediator.Send(new GetListDetails.Request(id), ct)
+ );
+ }
+}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj b/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj
index d2d4c4c760..924f81eb47 100644
--- a/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj
+++ b/services/Directory/FilterLists.Directory.Api/FilterLists.Directory.Api.csproj
@@ -8,6 +8,7 @@
+
diff --git a/services/Directory/FilterLists.Directory.Api/Program.cs b/services/Directory/FilterLists.Directory.Api/Program.cs
index d945d7e8b8..aa0044990b 100644
--- a/services/Directory/FilterLists.Directory.Api/Program.cs
+++ b/services/Directory/FilterLists.Directory.Api/Program.cs
@@ -1,39 +1,16 @@
+using FilterLists.Directory.Api;
+using FilterLists.Directory.Application;
+
var builder = WebApplication.CreateBuilder(args);
-// 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);
-}
\ No newline at end of file
+app.Run();
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Api/Properties/launchSettings.json b/services/Directory/FilterLists.Directory.Api/Properties/launchSettings.json
index 7abf85672f..c3363795da 100644
--- a/services/Directory/FilterLists.Directory.Api/Properties/launchSettings.json
+++ b/services/Directory/FilterLists.Directory.Api/Properties/launchSettings.json
@@ -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"
}
}
}
-}
+}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs
new file mode 100644
index 0000000000..5a45794c04
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs
@@ -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);
+ });
+ }
+}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj b/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj
new file mode 100644
index 0000000000..489f452c38
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Application/FilterLists.Directory.Application.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs
new file mode 100644
index 0000000000..f9c2a80f02
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs
@@ -0,0 +1,253 @@
+using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
+using JetBrains.Annotations;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+
+namespace FilterLists.Directory.Application.Queries;
+
+public static class GetListDetails
+{
+ private static readonly Func> 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 ListDetailsVm.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)
+ })
+ .Single(f => f.Id == id));
+
+ public sealed record Request(int Id) : IRequest;
+
+ internal sealed class Handler(QueryDbContext ctx) : IRequestHandler
+ {
+ public Task Handle(Request request, CancellationToken _)
+ {
+ // TODO: handle Single() exceptions
+ return Query(ctx, request.Id);
+ }
+ }
+
+ [PublicAPI]
+ public record ListDetailsVm
+ {
+ ///
+ /// The identifier.
+ ///
+ /// 301
+ public int Id { get; init; }
+
+ ///
+ /// The unique name in title case.
+ ///
+ /// EasyList
+ public required string Name { get; init; }
+
+ ///
+ /// The brief description in English (preferably quoted from the project).
+ ///
+ ///
+ /// 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.
+ ///
+ public string? Description { get; init; }
+
+ ///
+ /// The identifier of the License under which this FilterList is released.
+ ///
+ /// 4
+ public int LicenseId { get; init; }
+
+ ///
+ /// The identifiers of the Syntaxes implemented by this FilterList.
+ ///
+ /// [ 3 ]
+ public IEnumerable SyntaxIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the Languages targeted by this FilterList.
+ ///
+ /// [ 37 ]
+ public IEnumerable LanguageIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the Tags applied to this FilterList.
+ ///
+ /// [ 2 ]
+ public IEnumerable TagIds { get; init; } = [];
+
+ ///
+ /// The view URLs.
+ ///
+ public IEnumerable ViewUrls { get; init; } = [];
+
+ ///
+ /// The URL of the home page.
+ ///
+ /// https://easylist.to/
+ public Uri? HomeUrl { get; init; }
+
+ ///
+ /// The URL of the Tor / Onion page.
+ ///
+ /// null
+ public Uri? OnionUrl { get; init; }
+
+ ///
+ /// The URL of the policy/guidelines for the types of rules this FilterList includes.
+ ///
+ /// null
+ public Uri? PolicyUrl { get; init; }
+
+ ///
+ /// The URL of the submission/contact form for adding rules to this FilterList.
+ ///
+ /// null
+ public Uri? SubmissionUrl { get; init; }
+
+ ///
+ /// The URL of the GitHub Issues page.
+ ///
+ /// https://github.com/easylist/easylist/issues
+ public Uri? IssuesUrl { get; init; }
+
+ ///
+ /// The URL of the forum page.
+ ///
+ /// https://forums.lanik.us/viewforum.php?f=23
+ public Uri? ForumUrl { get; init; }
+
+ ///
+ /// The URL of the chat room.
+ ///
+ /// null
+ public Uri? ChatUrl { get; init; }
+
+ ///
+ /// The email address at which the project can be contacted.
+ ///
+ /// easylist@protonmail.com
+ public string? EmailAddress { get; init; }
+
+ ///
+ /// The URL at which donations to the project can be made.
+ ///
+ /// null
+ public Uri? DonateUrl { get; init; }
+
+ ///
+ /// The identifiers of the Maintainers of this FilterList.
+ ///
+ /// [ 7 ]
+ public IEnumerable MaintainerIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists from which this FilterList was forked.
+ ///
+ /// []
+ public IEnumerable UpstreamFilterListIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists that have been forked from this FilterList.
+ ///
+ /// [ 166, 565 ]
+ public IEnumerable ForkFilterListIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists that include this FilterList.
+ ///
+ /// []
+ public IEnumerable IncludedInFilterListIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists that this FilterList includes.
+ ///
+ /// [ 11, 13, 168 ]
+ public IEnumerable IncludesFilterListIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists that this FilterList depends upon.
+ ///
+ /// []
+ public IEnumerable DependencyFilterListIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the FilterLists dependent upon this FilterList.
+ ///
+ /// []
+ public IEnumerable DependentFilterListIds { get; init; } = [];
+
+ [PublicAPI]
+ public record ViewUrlVm
+ {
+ ///
+ /// The segment number of the URL for the FilterList (for multi-part lists).
+ ///
+ /// 1
+ public short SegmentNumber { get; init; }
+
+ ///
+ /// How primary the URL is for the FilterList segment (1 is original, 2+ is a mirror; unique per SegmentNumber)
+ ///
+ /// 1
+ public short Primariness { get; init; }
+
+ ///
+ /// The view URL.
+ ///
+ /// https://easylist.to/easylist/easylist.txt
+ public required Uri Url { get; init; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs
new file mode 100644
index 0000000000..b913b37e0f
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs
@@ -0,0 +1,112 @@
+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> Query =
+ EF.CompileAsyncQuery((QueryDbContext ctx) =>
+ ctx.FilterLists
+ .OrderBy(fl => fl.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)
+ })
+ );
+
+ public sealed record Request : IStreamRequest;
+
+ internal sealed class Handler(QueryDbContext ctx) : IStreamRequestHandler
+ {
+ public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
+ {
+ await foreach (var list in Query(ctx).WithCancellation(ct)) yield return list;
+ }
+ }
+
+ [PublicAPI]
+ public record ListVm
+ {
+ ///
+ /// The identifier.
+ ///
+ /// 301
+ public int Id { get; init; }
+
+ ///
+ /// The unique name in title case.
+ ///
+ /// EasyList
+ public required string Name { get; init; }
+
+ ///
+ /// The brief description in English (preferably quoted from the project).
+ ///
+ ///
+ /// 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.
+ ///
+ public string? Description { get; init; }
+
+ ///
+ /// The identifier of the License under which this FilterList is released.
+ ///
+ /// 4
+ public int LicenseId { get; init; }
+
+ ///
+ /// The identifiers of the Syntaxes implemented by this FilterList.
+ ///
+ /// [ 3 ]
+ public IEnumerable SyntaxIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the Languages targeted by this FilterList.
+ ///
+ /// [ 37 ]
+ public IEnumerable LanguageIds { get; init; } = [];
+
+ ///
+ /// The identifiers of the Tags applied to this FilterList.
+ ///
+ /// [ 2 ]
+ public IEnumerable TagIds { get; init; } = [];
+
+ ///
+ /// The primary view URL.
+ ///
+ /// https://easylist.to/easylist/easylist.txt
+ public Uri? PrimaryViewUrl { get; init; }
+
+ ///
+ /// The identifiers of the Maintainers of this FilterList.
+ ///
+ /// [ 7 ]
+ public IEnumerable MaintainerIds { get; init; } = [];
+ }
+}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Program.cs b/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Program.cs
index 23db377b24..310eafc069 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Program.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure.MigrationService/Program.cs
@@ -2,7 +2,7 @@
using FilterLists.Directory.Infrastructure.MigrationService;
var builder = Host.CreateApplicationBuilder(args);
-builder.AddInfrastructureForDbMaintenance();
+builder.AddInfrastructure();
builder.Services.AddHostedService();
var host = builder.Build();
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
index d894067389..efa8258b92 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
@@ -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("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"));
+ });
}
}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs
deleted file mode 100644
index b5ac7501a8..0000000000
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/IQueryContext.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
-
-namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
-
-public interface IQueryContext
-{
- IQueryable FilterLists { get; }
- IQueryable Languages { get; }
- IQueryable Licenses { get; }
- IQueryable Maintainers { get; }
- IQueryable Software { get; }
- IQueryable Syntaxes { get; }
- IQueryable Tags { get; }
-}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs
deleted file mode 100644
index 8155835696..0000000000
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryContext.cs
+++ /dev/null
@@ -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 FilterLists => dbContext.FilterLists.AsNoTracking();
- public IQueryable Languages => dbContext.Languages.AsNoTracking();
- public IQueryable Licenses => dbContext.Licenses.AsNoTracking();
- public IQueryable Maintainers => dbContext.Maintainers.AsNoTracking();
- public IQueryable Software => dbContext.Software.AsNoTracking();
- public IQueryable Syntaxes => dbContext.Syntaxes.AsNoTracking();
- public IQueryable Tags => dbContext.Tags.AsNoTracking();
-}
\ No newline at end of file
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs
index 7d49f6b680..bffc5b84ad 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs
@@ -3,6 +3,7 @@
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
+// TODO: explicitly make more readonly-ish
public class QueryDbContext(DbContextOptions options) : DbContext(options)
{
public DbSet FilterLists => Set();
@@ -13,18 +14,6 @@ public class QueryDbContext(DbContextOptions options) : DbContex
public DbSet Syntaxes => Set();
public DbSet Tags => Set();
- public override int SaveChanges(bool acceptAllChangesOnSuccess)
- {
- throw new InvalidOperationException("This context is read-only.");
- }
-
- public override Task 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");
diff --git a/services/FilterLists.AppHost/Program.cs b/services/FilterLists.AppHost/Program.cs
index 0c555c25bc..45bd97fbfd 100644
--- a/services/FilterLists.AppHost/Program.cs
+++ b/services/FilterLists.AppHost/Program.cs
@@ -18,6 +18,7 @@
var directoryApi = builder.AddProject("directoryapi")
.WithReference(directoryDb)
- .WithReference(appInsights);
+ .WithReference(appInsights)
+ .WithExternalHttpEndpoints();
builder.Build().Run();
\ No newline at end of file
diff --git a/services/FilterLists.sln b/services/FilterLists.sln
index e1ae5da4ac..bf2424d2d6 100644
--- a/services/FilterLists.sln
+++ b/services/FilterLists.sln
@@ -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