feat(dir): 🚧 scaffold CreateList command (part 2)

This commit is contained in:
Collin M. Barrett 2021-11-13 12:17:23 -06:00
parent fe87f5b4cf
commit 5b2a48d058
18 changed files with 134 additions and 127 deletions

View file

@ -1,6 +1,6 @@
using FilterLists.Directory.Domain.Aggregates;
using FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.Directory.Domain.Aggregates.Licenses;
using FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.Directory.Domain.Aggregates.FilterLists;
using FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
using FluentValidation;
using MediatR;
@ -9,6 +9,7 @@ namespace FilterLists.Directory.Application.Commands;
public static class CreateList
{
public record Command(string Name,
ICollection<FilterListViewUrl> ViewUrls,
string? Description = default,
int? LicenseId = default,
Uri? HomeUrl = default,
@ -19,7 +20,8 @@ public record Command(string Name,
Uri? ForumUrl = default,
Uri? ChatUrl = default,
string? EmailAddress = default,
Uri? DonateUrl = default) : IRequest<Response>;
Uri? DonateUrl = default,
string? ChangeReason = default) : IRequest<Response>;
internal class Validator : AbstractValidator<Command>
{
@ -33,24 +35,23 @@ public Validator()
internal class Handler : IRequestHandler<Command, Response>
{
private readonly IChangeRepository _changeRepo;
private readonly ILicenseRepository _licenseRepo;
private readonly ICommandContext _commandContext;
public Handler(IChangeRepository changeRepo, ILicenseRepository licenseRepo)
public Handler(ICommandContext commandContext)
{
_changeRepo = changeRepo;
_licenseRepo = licenseRepo;
_commandContext = commandContext;
}
public async Task<Response> 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;
? await _commandContext.Licenses.FindAsync(new object[] { request.LicenseId.Value },
cancellationToken) ?? throw new ArgumentException($"LicenseId {request.LicenseId} not found.",
nameof(request.LicenseId))
: default;
var filterList = new FilterList(request.Name,
var filterList = FilterList.Create(
request.Name,
request.Description,
license,
request.HomeUrl,
@ -61,14 +62,16 @@ public async Task<Response> Handle(Command request, CancellationToken cancellati
request.ForumUrl,
request.ChatUrl,
request.EmailAddress,
request.DonateUrl);
request.DonateUrl,
request.ViewUrls);
_commandContext.FilterLists.Add(filterList);
//Change change = null;
var change = Change.CreateFilterList(filterList, request.ChangeReason);
_commandContext.Changes.Add(change);
//await _changeRepo.AddAsync(change, cancellationToken);
//return new Response();
await _commandContext.SaveChangesAsync(cancellationToken);
throw new NotImplementedException();
return new Response();
}
}

View file

@ -1,5 +1,5 @@
using System.Text.Json;
using FilterLists.Directory.Domain.Aggregates.Licenses;
using FilterLists.Directory.Domain.Aggregates.FilterLists;
namespace FilterLists.Directory.Domain.Aggregates.Changes;
@ -9,26 +9,40 @@ private Change()
{
}
public Change(ChangeType type)
{
Type = type;
}
public ChangeType Type { get; init; }
// TODO: push json handling from Domain to Infrastructure
public JsonDocument? AggregateBefore { get; init; }
public JsonDocument? AggregateAfter { get; init; }
public DateTime CreatedAt { get; init; }
public string? CreatedReason { get; init; }
public string? Reason { get; init; }
public DateTime SubmittedAt { get; init; } = DateTime.UtcNow;
public DateTime? AppliedAt { get; init; }
public DateTime? RejectedAt { get; init; }
public string? RejectedReason { get; init; }
//public int? FilterListId { get; init; }
public FilterList? FilterList { get; init; }
//public string? LanguageIso6391 { get; init; }
//public Language? Language { get; }
public License? License { get; init; }
//public int? LicenseId { get; init; }
//public License? License { get; }
//public int? MaintainerId { get; init; }
//public Maintainer? Maintainer { get; }
//public int? SoftwareId { get; init; }
//public Software? Software { get; }
//public int? SyntaxId { get; init; }
//public Syntax? Syntax { get; }
//public int? TagId { get; init; }
//public Tag? Tag { get; }
public static Change CreateFilterList(FilterList filterList, string? reason)
{
return new Change
{
Type = ChangeType.Create,
AggregateAfter = JsonSerializer.SerializeToDocument(filterList),
Reason = reason,
FilterList = filterList
};
}
}

View file

@ -2,7 +2,7 @@
public enum ChangeType
{
Insert,
Create,
Update,
Delete
}

View file

@ -1,6 +0,0 @@
namespace FilterLists.Directory.Domain.Aggregates.Changes;
public interface IChangeRepository
{
Task AddAsync(Change change, CancellationToken cancellationToken);
}

View file

@ -1,6 +1,6 @@
using FilterLists.Directory.Domain.Aggregates.Licenses;
namespace FilterLists.Directory.Domain.Aggregates;
namespace FilterLists.Directory.Domain.Aggregates.FilterLists;
public class FilterList
{
@ -8,33 +8,6 @@ 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; }
@ -47,4 +20,44 @@ public FilterList(string name,
public Uri? ChatUrl { get; private init; }
public string? EmailAddress { get; private init; }
public Uri? DonateUrl { get; private init; }
public IEnumerable<FilterListViewUrl> ViewUrls { get; init; } = new HashSet<FilterListViewUrl>();
public static FilterList Create(
string name,
string? description,
License? license,
Uri? homeUrl,
Uri? onionUrl,
Uri? policyUrl,
Uri? submissionUrl,
Uri? issuesUrl,
Uri? forumUrl,
Uri? chatUrl,
string? emailAddress,
Uri? donateUrl,
ICollection<FilterListViewUrl> viewUrls)
{
if (viewUrls.Count == 0)
{
// TODO: create and handle DomainExceptions
throw new ArgumentException("At lest one view URL is required", nameof(viewUrls));
}
return new FilterList
{
Name = name,
Description = description,
License = license,
HomeUrl = homeUrl,
OnionUrl = onionUrl,
PolicyUrl = policyUrl,
SubmissionUrl = submissionUrl,
IssuesUrl = issuesUrl,
ForumUrl = forumUrl,
ChatUrl = chatUrl,
EmailAddress = emailAddress,
DonateUrl = donateUrl,
ViewUrls = viewUrls
};
}
}

View file

@ -0,0 +1,8 @@
namespace FilterLists.Directory.Domain.Aggregates.FilterLists;
public class FilterListViewUrl
{
public short SegmentNumber { get; init; }
public short Primariness { get; init; }
public Uri Url { get; init; } = null!;
}

View file

@ -1,6 +0,0 @@
namespace FilterLists.Directory.Domain.Aggregates.Licenses;
public interface ILicenseRepository
{
ValueTask<License?> GetByIdAsync(int id, CancellationToken cancellationToken);
}

View file

@ -6,19 +6,6 @@ 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; }

View file

@ -1,5 +1,4 @@
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;
@ -47,10 +46,6 @@ public static void AddInfrastructure(this IServiceCollection services, IConfigur
});
services.AddScoped<IQueryContext, QueryContext>();
services.AddScoped<ICommandContext, CommandDbContext>();
services.Scan(s => s.FromAssembliesOf(typeof(ConfigurationExtensions))
.AddClasses(f => f.InNamespaceOf<ChangeRepository>(), false)
.AsImplementedInterfaces()
.WithScopedLifetime());
}
public static void UseInfrastructure(this IApplicationBuilder app)

View file

@ -20,7 +20,6 @@
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0" />
<PackageReference Include="Scrutor" Version="3.3.0" />
</ItemGroup>
<ItemGroup>

View file

@ -1,4 +1,5 @@
using FilterLists.Directory.Domain.Aggregates;
using FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.Directory.Domain.Aggregates.FilterLists;
using FilterLists.Directory.Domain.Aggregates.Licenses;
using FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
using Microsoft.EntityFrameworkCore;
@ -11,6 +12,7 @@ public CommandDbContext(DbContextOptions<CommandDbContext> options) : base(optio
{
}
public DbSet<Change> Changes => Set<Change>();
public DbSet<FilterList> FilterLists => Set<FilterList>();
public DbSet<License> Licenses => Set<License>();

View file

@ -1,11 +1,14 @@
using FilterLists.Directory.Domain.Aggregates;
using FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.Directory.Domain.Aggregates.FilterLists;
using FilterLists.Directory.Domain.Aggregates.Licenses;
using Microsoft.EntityFrameworkCore;
namespace FilterLists.Directory.Infrastructure.Persistence.Commands.Context;
internal interface ICommandContext
public interface ICommandContext
{
DbSet<Change> Changes { get; }
DbSet<FilterList> FilterLists { get; }
DbSet<License> Licenses { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

View file

@ -0,0 +1,13 @@
using FilterLists.Directory.Domain.Aggregates.Changes;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
internal class ChangeTypeConfiguration : IEntityTypeConfiguration<Change>
{
public virtual void Configure(EntityTypeBuilder<Change> builder)
{
builder.Property<int>(nameof(Queries.Entities.Change.Id));
}
}

View file

@ -1,4 +1,4 @@
using FilterLists.Directory.Domain.Aggregates;
using FilterLists.Directory.Domain.Aggregates.FilterLists;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

View file

@ -0,0 +1,14 @@
using FilterLists.Directory.Domain.Aggregates.FilterLists;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace FilterLists.Directory.Infrastructure.Persistence.Commands.EntityTypeConfigurations;
internal class FilterListViewUrlTypeConfiguration : IEntityTypeConfiguration<FilterListViewUrl>
{
public virtual void Configure(EntityTypeBuilder<FilterListViewUrl> builder)
{
builder.ToTable(nameof(FilterListViewUrl) + "s");
builder.Property<int>(nameof(Queries.Entities.FilterListViewUrl.Id));
}
}

View file

@ -1,11 +0,0 @@
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();
}
}

View file

@ -1,19 +0,0 @@
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<License?> GetByIdAsync(int id, CancellationToken cancellationToken)
{
return _context.Licenses.FindAsync(new object[] { id }, cancellationToken);
}
}

View file

@ -11,8 +11,8 @@ public record Change
public ChangeType Type { get; init; }
public JsonDocument? AggregateBefore { get; init; }
public JsonDocument? AggregateAfter { get; init; }
public DateTime CreatedAt { get; init; }
public string? CreatedReason { get; init; }
public string? Reason { get; init; }
public DateTime SubmittedAt { get; init; }
public DateTime? AppliedAt { get; init; }
public DateTime? RejectedAt { get; init; }
public string? RejectedReason { get; init; }
@ -36,7 +36,5 @@ internal class UpdateConfiguration : IEntityTypeConfiguration<Change>
{
public virtual void Configure(EntityTypeBuilder<Change> builder)
{
builder.Property(c => c.CreatedAt)
.HasDefaultValueSql("now()");
}
}