diff --git a/services/Directory/FilterLists.Directory.Api/Controllers/ListsController.cs b/services/Directory/FilterLists.Directory.Api/Controllers/ListsController.cs
index daf295203..dd5a43845 100644
--- a/services/Directory/FilterLists.Directory.Api/Controllers/ListsController.cs
+++ b/services/Directory/FilterLists.Directory.Api/Controllers/ListsController.cs
@@ -1,4 +1,5 @@
using FilterLists.Directory.Api.Contracts.Models;
+using FilterLists.Directory.Application.Commands;
using FilterLists.Directory.Application.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@@ -16,7 +17,7 @@ public ListsController(IMemoryCache cache, IMediator mediator) : base(cache)
}
///
- /// Gets the FilterLists..
+ /// Gets the FilterLists.
///
/// The cancellation token.
/// The FilterLists.
@@ -27,6 +28,18 @@ public Task Get(CancellationToken cancellationToken)
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLists.Query(), cancellationToken));
}
+ ///
+ /// Creates the FilterList.
+ ///
+ /// The command.
+ /// The cancellation token.
+ [HttpPost]
+ [ProducesResponseType(typeof(CreateList.Response), StatusCodes.Status200OK)]
+ public async Task Create(CreateList.Command command, CancellationToken cancellationToken)
+ {
+ return Ok(await _mediator.Send(command, cancellationToken));
+ }
+
///
/// Gets the details of the FilterList.
///
diff --git a/services/Directory/FilterLists.Directory.Application/Commands/CreateList.cs b/services/Directory/FilterLists.Directory.Application/Commands/CreateList.cs
new file mode 100644
index 000000000..b7df3e9be
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Application/Commands/CreateList.cs
@@ -0,0 +1,78 @@
+using FilterLists.Directory.Domain.Aggregates;
+using FilterLists.Directory.Domain.Aggregates.Changes;
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+using FluentValidation;
+using MediatR;
+
+namespace FilterLists.Directory.Application.Commands;
+
+public static class CreateList
+{
+ public record Command(string Name,
+ string? Description = default,
+ int? LicenseId = default,
+ Uri? HomeUrl = default,
+ Uri? OnionUrl = default,
+ Uri? PolicyUrl = default,
+ Uri? SubmissionUrl = default,
+ Uri? IssuesUrl = default,
+ Uri? ForumUrl = default,
+ Uri? ChatUrl = default,
+ string? EmailAddress = default,
+ Uri? DonateUrl = default) : IRequest;
+
+ internal class Validator : AbstractValidator
+ {
+ public Validator()
+ {
+ RuleFor(c => c.LicenseId)
+ .GreaterThanOrEqualTo(0)
+ .When(c => c.LicenseId != null);
+ }
+ }
+
+ internal class Handler : IRequestHandler
+ {
+ private readonly IChangeRepository _changeRepo;
+ private readonly ILicenseRepository _licenseRepo;
+
+ public Handler(IChangeRepository changeRepo, ILicenseRepository licenseRepo)
+ {
+ _changeRepo = changeRepo;
+ _licenseRepo = licenseRepo;
+ }
+
+ public async Task Handle(Command request, CancellationToken cancellationToken)
+ {
+ var license = request.LicenseId != null
+ ? await _licenseRepo.GetByIdAsync(request.LicenseId.Value, cancellationToken) ??
+ throw new ArgumentException($"LicenseId {request.LicenseId} not found.",
+ nameof(request.LicenseId))
+ : null;
+
+ var filterList = new FilterList(request.Name,
+ request.Description,
+ license,
+ request.HomeUrl,
+ request.OnionUrl,
+ request.PolicyUrl,
+ request.SubmissionUrl,
+ request.IssuesUrl,
+ request.ForumUrl,
+ request.ChatUrl,
+ request.EmailAddress,
+ request.DonateUrl);
+
+ //Change change = null;
+
+ //await _changeRepo.AddAsync(change, cancellationToken);
+ //return new Response();
+
+ throw new NotImplementedException();
+ }
+ }
+
+ public record Response
+ {
+ }
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/AggregateRoot.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/AggregateRoot.cs
new file mode 100644
index 000000000..c884d1a83
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/AggregateRoot.cs
@@ -0,0 +1,12 @@
+namespace FilterLists.Directory.Domain.Aggregates.Changes;
+
+public enum AggregateRoot
+{
+ FilterList,
+ Language,
+ License,
+ Maintainer,
+ Software,
+ Syntax,
+ Tag
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/Change.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/Change.cs
new file mode 100644
index 000000000..9fef1081c
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/Change.cs
@@ -0,0 +1,27 @@
+using System.Text.Json;
+
+namespace FilterLists.Directory.Domain.Aggregates.Changes;
+
+public class Change
+{
+ private Change()
+ {
+ }
+
+ public Change(ChangeType type, AggregateRoot aggregateRoot, int aggregateRootId, JsonDocument? aggregate)
+ {
+ Type = type;
+ AggregateRoot = aggregateRoot;
+ AggregateRootId = aggregateRootId;
+ Aggregate = aggregate;
+ }
+
+ public ChangeType Type { get; private init; }
+ public AggregateRoot AggregateRoot { get; private init; }
+ public int AggregateRootId { get; private init; }
+ public JsonDocument? Aggregate { get; private init; }
+ public DateTime CreatedAt { get; private init; }
+ public DateTime? AppliedAt { get; private init; }
+ public DateTime? RejectedAt { get; private init; }
+ public string? RejectedReason { get; private init; }
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/ChangeType.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/ChangeType.cs
new file mode 100644
index 000000000..a703aa664
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/ChangeType.cs
@@ -0,0 +1,8 @@
+namespace FilterLists.Directory.Domain.Aggregates.Changes;
+
+public enum ChangeType
+{
+ Insert,
+ Update,
+ Delete
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/IChangeRepository.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/IChangeRepository.cs
new file mode 100644
index 000000000..f066bf5c3
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Changes/IChangeRepository.cs
@@ -0,0 +1,6 @@
+namespace FilterLists.Directory.Domain.Aggregates.Changes;
+
+public interface IChangeRepository
+{
+ Task AddAsync(Change change, CancellationToken cancellationToken);
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/FilterList.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/FilterList.cs
index 9896531da..05e4858de 100644
--- a/services/Directory/FilterLists.Directory.Domain/Aggregates/FilterList.cs
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/FilterList.cs
@@ -1,18 +1,50 @@
-namespace FilterLists.Directory.Domain.Aggregates;
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+
+namespace FilterLists.Directory.Domain.Aggregates;
public class FilterList
{
- public int Id { get; private set; }
- public string Name { get; private set; } = null!;
- public string? Description { get; private set; }
- public int? LicenseId { get; private set; }
- public Uri? HomeUrl { get; private set; }
- public Uri? OnionUrl { get; private set; }
- public Uri? PolicyUrl { get; private set; }
- public Uri? SubmissionUrl { get; private set; }
- public Uri? IssuesUrl { get; private set; }
- public Uri? ForumUrl { get; private set; }
- public Uri? ChatUrl { get; private set; }
- public string? EmailAddress { get; private set; }
- public Uri? DonateUrl { get; private set; }
+ private FilterList()
+ {
+ }
+
+ public FilterList(string name,
+ string? description,
+ License? license,
+ Uri? homeUrl,
+ Uri? onionUrl,
+ Uri? policyUrl,
+ Uri? submissionUrl,
+ Uri? issuesUrl,
+ Uri? forumUrl,
+ Uri? chatUrl,
+ string? emailAddress,
+ Uri? donateUrl)
+ {
+ Name = name;
+ Description = description;
+ License = license;
+ HomeUrl = homeUrl;
+ OnionUrl = onionUrl;
+ PolicyUrl = policyUrl;
+ SubmissionUrl = submissionUrl;
+ IssuesUrl = issuesUrl;
+ ForumUrl = forumUrl;
+ ChatUrl = chatUrl;
+ EmailAddress = emailAddress;
+ DonateUrl = donateUrl;
+ }
+
+ public string Name { get; private init; } = null!;
+ public string? Description { get; private init; }
+ public License? License { get; private init; }
+ public Uri? HomeUrl { get; private init; }
+ public Uri? OnionUrl { get; private init; }
+ public Uri? PolicyUrl { get; private init; }
+ public Uri? SubmissionUrl { get; private init; }
+ public Uri? IssuesUrl { get; private init; }
+ public Uri? ForumUrl { get; private init; }
+ public Uri? ChatUrl { get; private init; }
+ public string? EmailAddress { get; private init; }
+ public Uri? DonateUrl { get; private init; }
}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/ILicenseRepository.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/ILicenseRepository.cs
new file mode 100644
index 000000000..cc663f0f7
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/ILicenseRepository.cs
@@ -0,0 +1,6 @@
+namespace FilterLists.Directory.Domain.Aggregates.Licenses;
+
+public interface ILicenseRepository
+{
+ ValueTask GetByIdAsync(int id, CancellationToken cancellationToken);
+}
diff --git a/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/License.cs b/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/License.cs
new file mode 100644
index 000000000..3fd3dc0bc
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Domain/Aggregates/Licenses/License.cs
@@ -0,0 +1,27 @@
+namespace FilterLists.Directory.Domain.Aggregates.Licenses;
+
+public class License
+{
+ private License()
+ {
+ }
+
+ public License(string name,
+ Uri? url,
+ bool? permitsModification,
+ bool? permitsDistribution,
+ bool? permitsCommercialUse)
+ {
+ Name = name;
+ Url = url;
+ PermitsModification = permitsModification ?? false;
+ PermitsDistribution = permitsDistribution ?? false;
+ PermitsCommercialUse = permitsCommercialUse ?? false;
+ }
+
+ public string Name { get; private init; } = null!;
+ public Uri? Url { get; private init; }
+ public bool PermitsModification { get; private init; }
+ public bool PermitsDistribution { get; private init; }
+ public bool PermitsCommercialUse { get; private init; }
+}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
index 0c678be27..d2e8355de 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs
@@ -1,4 +1,5 @@
using FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
+using FilterLists.Directory.Infrastructure.Persistence.Commands.Repositories;
using FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
using FilterLists.SharedKernel.Logging;
using Microsoft.AspNetCore.Builder;
@@ -32,10 +33,24 @@ public static void AddInfrastructure(this IServiceCollection services, IConfigur
o.EnableSensitiveDataLogging();
#else
;
+#endif
+ });
+ services.AddDbContextPool(o =>
+ {
+ o.UseNpgsql(configuration.GetConnectionString("DirectoryConnection"))
+#if DEBUG
+ .LogTo(Console.WriteLine, LogLevel.Information);
+ o.EnableSensitiveDataLogging();
+#else
+ ;
#endif
});
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
+ services.Scan(s => s.FromAssembliesOf(typeof(ConfigurationExtensions))
+ .AddClasses(f => f.InNamespaceOf(), false)
+ .AsImplementedInterfaces()
+ .WithScopedLifetime());
}
public static void UseInfrastructure(this IApplicationBuilder app)
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj b/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj
index 7d82668d4..d345735da 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj
+++ b/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj
@@ -20,6 +20,7 @@
+
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandContext.cs
deleted file mode 100644
index ab96858d9..000000000
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandContext.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
-
-internal class CommandContext : ICommandContext, IAsyncDisposable
-{
- private readonly CommandDbContext _dbContext;
-
- public CommandContext(CommandDbContext dbContext)
- {
- _dbContext = dbContext;
- }
-
- public async ValueTask DisposeAsync()
- {
- await _dbContext.DisposeAsync();
- }
-}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandDbContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandDbContext.cs
index 72cf72ad5..fa1c79186 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandDbContext.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/CommandDbContext.cs
@@ -1,15 +1,22 @@
-using Microsoft.EntityFrameworkCore;
+using FilterLists.Directory.Domain.Aggregates;
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+using FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
+using Microsoft.EntityFrameworkCore;
namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
-public class CommandDbContext : DbContext
+internal class CommandDbContext : DbContext, ICommandContext
{
public CommandDbContext(DbContextOptions options) : base(options)
{
}
+ public DbSet FilterLists => Set();
+ public DbSet Licenses => Set();
+
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
+ modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly,
+ type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace);
}
}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/ICommandContext.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/ICommandContext.cs
index f18b4e0a2..53dd0453f 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/ICommandContext.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Context/ICommandContext.cs
@@ -1,5 +1,11 @@
-namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
+using FilterLists.Directory.Domain.Aggregates;
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+using Microsoft.EntityFrameworkCore;
-public interface ICommandContext
+namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
+
+internal interface ICommandContext
{
+ DbSet FilterLists { get; }
+ DbSet Licenses { get; }
}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/FilterListEntityTypeConfiguration.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/FilterListEntityTypeConfiguration.cs
new file mode 100644
index 000000000..515f76cff
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/FilterListEntityTypeConfiguration.cs
@@ -0,0 +1,13 @@
+using FilterLists.Directory.Domain.Aggregates;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
+
+internal class FilterListTypeConfiguration : IEntityTypeConfiguration
+{
+ public virtual void Configure(EntityTypeBuilder builder)
+ {
+ builder.Property(nameof(Queries.Entities.FilterList.Id));
+ }
+}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/LicenseEntityTypeConfiguration.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/LicenseEntityTypeConfiguration.cs
new file mode 100644
index 000000000..ddff32b36
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/EntityTypeConfigurations/LicenseEntityTypeConfiguration.cs
@@ -0,0 +1,13 @@
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
+
+internal class LicenseTypeConfiguration : IEntityTypeConfiguration
+{
+ public virtual void Configure(EntityTypeBuilder builder)
+ {
+ builder.Property(nameof(Queries.Entities.License.Id));
+ }
+}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/ChangeRepository.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/ChangeRepository.cs
new file mode 100644
index 000000000..91423c64f
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/ChangeRepository.cs
@@ -0,0 +1,11 @@
+using FilterLists.Directory.Domain.Aggregates.Changes;
+
+namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Repositories;
+
+internal class ChangeRepository : IChangeRepository
+{
+ public Task AddAsync(Change change, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/LicenseRepository.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/LicenseRepository.cs
new file mode 100644
index 000000000..c6ca752dd
--- /dev/null
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Commands/Repositories/LicenseRepository.cs
@@ -0,0 +1,19 @@
+using FilterLists.Directory.Domain.Aggregates.Licenses;
+using FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
+
+namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Repositories;
+
+internal class LicenseRepository : ILicenseRepository
+{
+ private readonly ICommandContext _context;
+
+ public LicenseRepository(ICommandContext context)
+ {
+ _context = context;
+ }
+
+ public ValueTask GetByIdAsync(int id, CancellationToken cancellationToken)
+ {
+ return _context.Licenses.FindAsync(new object[] { id }, cancellationToken);
+ }
+}
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 422092dce..28b8150a4 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Context/QueryDbContext.cs
@@ -1,6 +1,8 @@
-using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
+using FilterLists.Directory.Domain.Aggregates.Changes;
+using FilterLists.Directory.Infrastructure.Persistence.Queries.Entities;
using Microsoft.EntityFrameworkCore;
using Npgsql;
+using Change = FilterLists.Directory.Infrastructure.Persistence.Queries.Entities.Change;
namespace FilterLists.Directory.Infrastructure.Persistence.Queries.Context;
@@ -39,7 +41,8 @@ public override Task SaveChangesAsync(
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
+ modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly,
+ type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace);
modelBuilder.HasPostgresEnum();
modelBuilder.HasPostgresEnum();
}
diff --git a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Change.cs b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Change.cs
index 9ddd07966..67c41c8cf 100644
--- a/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Change.cs
+++ b/services/Directory/FilterLists.Directory.Infrastructure/Persistence/Queries/Entities/Change.cs
@@ -1,4 +1,5 @@
using System.Text.Json;
+using FilterLists.Directory.Domain.Aggregates.Changes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
@@ -17,24 +18,6 @@ public class Change
public string? RejectedReason { get; init; }
}
-public enum ChangeType
-{
- Insert,
- Update,
- Delete
-}
-
-public enum AggregateRoot
-{
- FilterList,
- Language,
- License,
- Maintainer,
- Software,
- Syntax,
- Tag
-}
-
internal class UpdateConfiguration : IEntityTypeConfiguration
{
public virtual void Configure(EntityTypeBuilder builder)