mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
feat(dir): ✨🚧 scaffold CreateList command
This commit is contained in:
parent
94dab0d15c
commit
2c5f8363e1
20 changed files with 321 additions and 57 deletions
|
|
@ -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)
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FilterLists..
|
||||
/// Gets the FilterLists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The FilterLists.</returns>
|
||||
|
|
@ -27,6 +28,18 @@ public Task<IActionResult> Get(CancellationToken cancellationToken)
|
|||
return CacheGetOrCreateAsync(() => _mediator.Send(new GetLists.Query(), cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the FilterList.
|
||||
/// </summary>
|
||||
/// <param name="command">The command.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(CreateList.Response), StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> Create(CreateList.Command command, CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await _mediator.Send(command, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the details of the FilterList.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -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<Response>;
|
||||
|
||||
internal class Validator : AbstractValidator<Command>
|
||||
{
|
||||
public Validator()
|
||||
{
|
||||
RuleFor(c => c.LicenseId)
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.When(c => c.LicenseId != null);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Handler : IRequestHandler<Command, Response>
|
||||
{
|
||||
private readonly IChangeRepository _changeRepo;
|
||||
private readonly ILicenseRepository _licenseRepo;
|
||||
|
||||
public Handler(IChangeRepository changeRepo, ILicenseRepository licenseRepo)
|
||||
{
|
||||
_changeRepo = changeRepo;
|
||||
_licenseRepo = licenseRepo;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace FilterLists.Directory.Domain.Aggregates.Changes;
|
||||
|
||||
public enum AggregateRoot
|
||||
{
|
||||
FilterList,
|
||||
Language,
|
||||
License,
|
||||
Maintainer,
|
||||
Software,
|
||||
Syntax,
|
||||
Tag
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace FilterLists.Directory.Domain.Aggregates.Changes;
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
Insert,
|
||||
Update,
|
||||
Delete
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace FilterLists.Directory.Domain.Aggregates.Changes;
|
||||
|
||||
public interface IChangeRepository
|
||||
{
|
||||
Task AddAsync(Change change, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace FilterLists.Directory.Domain.Aggregates.Licenses;
|
||||
|
||||
public interface ILicenseRepository
|
||||
{
|
||||
ValueTask<License?> GetByIdAsync(int id, CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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<CommandDbContext>(o =>
|
||||
{
|
||||
o.UseNpgsql(configuration.GetConnectionString("DirectoryConnection"))
|
||||
#if DEBUG
|
||||
.LogTo(Console.WriteLine, LogLevel.Information);
|
||||
o.EnableSensitiveDataLogging();
|
||||
#else
|
||||
;
|
||||
#endif
|
||||
});
|
||||
services.AddScoped<IQueryContext, QueryContext>();
|
||||
services.AddScoped<ICommandContext, CommandContext>();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<CommandDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<FilterList> FilterLists => Set<FilterList>();
|
||||
public DbSet<License> Licenses => Set<License>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly,
|
||||
type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FilterList> FilterLists { get; }
|
||||
DbSet<License> Licenses { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<FilterList>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<FilterList> builder)
|
||||
{
|
||||
builder.Property<int>(nameof(Queries.Entities.FilterList.Id));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<License>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<License> builder)
|
||||
{
|
||||
builder.Property<int>(nameof(Queries.Entities.License.Id));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<License?> GetByIdAsync(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
return _context.Licenses.FindAsync(new object[] { id }, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<int> SaveChangesAsync(
|
|||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly,
|
||||
type => type.Namespace == typeof(FilterListTypeConfiguration).Namespace);
|
||||
modelBuilder.HasPostgresEnum<ChangeType>();
|
||||
modelBuilder.HasPostgresEnum<AggregateRoot>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Change>
|
||||
{
|
||||
public virtual void Configure(EntityTypeBuilder<Change> builder)
|
||||
|
|
|
|||
Loading…
Reference in a new issue