diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs index 9b0f836d..990723b9 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -1,13 +1,11 @@ using Microsoft.EntityFrameworkCore; using SSW.CleanArchitecture.Domain.Heroes; using SSW.CleanArchitecture.Domain.Teams; -using SSW.CleanArchitecture.Domain.TodoItems; namespace SSW.CleanArchitecture.Application.Common.Interfaces; public interface IApplicationDbContext { - DbSet TodoItems { get; } DbSet Heroes { get; } DbSet Teams { get; } Task SaveChangesAsync(CancellationToken cancellationToken = default); diff --git a/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommand.cs b/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommand.cs deleted file mode 100644 index 7f5ff9a5..00000000 --- a/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SSW.CleanArchitecture.Application.Common.Exceptions; -using SSW.CleanArchitecture.Application.Common.Interfaces; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CompleteTodoItem; - -public record CompleteTodoItemCommand(Guid TodoItemId) : IRequest; - -// ReSharper disable once UnusedType.Global -public class CompleteTodoItemCommandHandler(IApplicationDbContext dbContext) - : IRequestHandler -{ - public async Task Handle(CompleteTodoItemCommand request, CancellationToken cancellationToken) - { - var todoItemId = new TodoItemId(request.TodoItemId); - - var todoItem = await dbContext.TodoItems - .WithSpecification(new TodoItemByIdSpec(todoItemId)) - .FirstOrDefaultAsync(cancellationToken); - - if (todoItem is null) - throw new NotFoundException(nameof(TodoItem), todoItemId); - - todoItem.Complete(); - - await dbContext.SaveChangesAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommandValidator.cs b/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommandValidator.cs deleted file mode 100644 index 94592972..00000000 --- a/src/Application/Features/TodoItems/Commands/CompleteTodoItem/CompleteTodoItemCommandValidator.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CompleteTodoItem; - -public class CompleteTodoItemCommandValidator : AbstractValidator -{ - public CompleteTodoItemCommandValidator() - { - RuleFor(p => p.TodoItemId) - .NotEmpty(); - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs b/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs deleted file mode 100644 index fabaac60..00000000 --- a/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using SSW.CleanArchitecture.Application.Common.Interfaces; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CreateTodoItem; - -public record CreateTodoItemCommand(string Title) : IRequest; - -// ReSharper disable once UnusedType.Global -public class CreateTodoItemCommandHandler(IApplicationDbContext dbContext) - : IRequestHandler -{ - public async Task Handle(CreateTodoItemCommand request, CancellationToken cancellationToken) - { - var todoItem = TodoItem.Create(request.Title!); - - await dbContext.TodoItems.AddAsync(todoItem, cancellationToken); - await dbContext.SaveChangesAsync(cancellationToken); - - return todoItem.Id.Value; - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs b/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs deleted file mode 100644 index bf8ced38..00000000 --- a/src/Application/Features/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using SSW.CleanArchitecture.Application.Common.Interfaces; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CreateTodoItem; - -public class CreateTodoItemCommandValidator : AbstractValidator -{ - private readonly IApplicationDbContext _dbContext; - - public CreateTodoItemCommandValidator(IApplicationDbContext dbContext) - { - _dbContext = dbContext; - - RuleFor(p => p.Title) - .NotEmpty() - .MaximumLength(200) - .MustAsync(BeUniqueTitle).WithMessage("'{PropertyName}' must be unique"); - } - - // TODO DM: Consider pushing this business validation to the Domain - private async Task BeUniqueTitle(string title, CancellationToken cancellationToken) - { - var spec = new TodoItemByTitleSpec(title); - - var exists = await _dbContext.TodoItems - .WithSpecification(spec) - .AnyAsync(cancellationToken: cancellationToken); - - return !exists; - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs b/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs deleted file mode 100644 index 3291227f..00000000 --- a/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Extensions.Logging; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.EventHandlers; - -public class TodoItemCreatedEventHandler(ILogger logger) - : INotificationHandler -{ - public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken) - { - logger.LogInformation("TodoItemCreatedEventHandler: {Title} was created", notification.Item.Title); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsMapping.cs b/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsMapping.cs deleted file mode 100644 index 7ed92d25..00000000 --- a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsMapping.cs +++ /dev/null @@ -1,12 +0,0 @@ -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Queries.GetAllTodoItems; - -public class GetAllTodoItemsMapping : Profile -{ - public GetAllTodoItemsMapping() - { - CreateMap() - .ForMember(d => d.Id, opt => opt.MapFrom(s => s.Id.Value)); - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsQuery.cs b/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsQuery.cs deleted file mode 100644 index 5ebca054..00000000 --- a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/GetAllTodoItemsQuery.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AutoMapper.QueryableExtensions; -using Microsoft.EntityFrameworkCore; -using SSW.CleanArchitecture.Application.Common.Interfaces; - -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Queries.GetAllTodoItems; - -public record GetAllTodoItemsQuery : IRequest>; - -public class GetAllTodoItemsQueryHandler( - IMapper mapper, - IApplicationDbContext dbContext) : IRequestHandler> -{ - public async Task> Handle( - GetAllTodoItemsQuery request, - CancellationToken cancellationToken) - { - return await dbContext.TodoItems - .ProjectTo(mapper.ConfigurationProvider) - .ToListAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/TodoItemDto.cs b/src/Application/Features/TodoItems/Queries/GetAllTodoItems/TodoItemDto.cs deleted file mode 100644 index 03407a66..00000000 --- a/src/Application/Features/TodoItems/Queries/GetAllTodoItems/TodoItemDto.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SSW.CleanArchitecture.Application.Features.TodoItems.Queries.GetAllTodoItems; - -public class TodoItemDto -{ - public Guid Id { get; set; } - public string? Title { get; set; } - public bool Done { get; set; } -} \ No newline at end of file diff --git a/src/Domain/TodoItems/PriorityLevel.cs b/src/Domain/TodoItems/PriorityLevel.cs deleted file mode 100644 index e01c6d46..00000000 --- a/src/Domain/TodoItems/PriorityLevel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SSW.CleanArchitecture.Domain.TodoItems; - -public enum PriorityLevel -{ - None = 0, - Low = 1, - Medium = 2, - High = 3 -} \ No newline at end of file diff --git a/src/Domain/TodoItems/TodoItem.cs b/src/Domain/TodoItems/TodoItem.cs deleted file mode 100644 index 4a405b53..00000000 --- a/src/Domain/TodoItems/TodoItem.cs +++ /dev/null @@ -1,53 +0,0 @@ -using SSW.CleanArchitecture.Domain.Common.Base; - -namespace SSW.CleanArchitecture.Domain.TodoItems; - -// For strongly typed IDs, check out the rule: https://www.ssw.com.au/rules/do-you-use-strongly-typed-ids/ -public readonly record struct TodoItemId(Guid Value); - -public class TodoItem : BaseEntity -{ - // NOTE: private setters for behavior we want to encapsulate, and public setters for properties that don't have behavior - - public string? Title { get; private set; } - public string? Note { get; set; } - public PriorityLevel Priority { get; set; } - public DateTime Reminder { get; set; } - public bool Done { get; private set; } - - // Needed for EF - private TodoItem() { } - - public static TodoItem Create(string title) - { - ArgumentException.ThrowIfNullOrEmpty(title, nameof(title)); - - var todoItem = new TodoItem - { - Title = title, - Priority = PriorityLevel.None, - Done = false - }; - - todoItem.AddDomainEvent(new TodoItemCreatedEvent(todoItem)); - - return todoItem; - } - - public static TodoItem Create(string title, string note, PriorityLevel priority, DateTime reminder) - { - var todoItem = Create(title); - todoItem.Note = note; - todoItem.Priority = priority; - todoItem.Reminder = reminder; - - return todoItem; - } - - public void Complete() - { - Done = true; - - AddDomainEvent(new TodoItemCompletedEvent(this)); - } -} \ No newline at end of file diff --git a/src/Domain/TodoItems/TodoItemByIdSpec.cs b/src/Domain/TodoItems/TodoItemByIdSpec.cs deleted file mode 100644 index e2cf28fe..00000000 --- a/src/Domain/TodoItems/TodoItemByIdSpec.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ardalis.Specification; - -namespace SSW.CleanArchitecture.Domain.TodoItems; - -public sealed class TodoItemByIdSpec : SingleResultSpecification -{ - public TodoItemByIdSpec(TodoItemId todoItemId) - { - Query.Where(t => t.Id == todoItemId); - } -} \ No newline at end of file diff --git a/src/Domain/TodoItems/TodoItemByTitleSpec.cs b/src/Domain/TodoItems/TodoItemByTitleSpec.cs deleted file mode 100644 index c0f1ac33..00000000 --- a/src/Domain/TodoItems/TodoItemByTitleSpec.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ardalis.Specification; - -namespace SSW.CleanArchitecture.Domain.TodoItems; - -public sealed class TodoItemByTitleSpec : Specification -{ - public TodoItemByTitleSpec(string title) - { - Query.Where(i => i.Title == title); - } -} diff --git a/src/Domain/TodoItems/TodoItemCompletedEvent.cs b/src/Domain/TodoItems/TodoItemCompletedEvent.cs deleted file mode 100644 index e0b8a9a2..00000000 --- a/src/Domain/TodoItems/TodoItemCompletedEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -using SSW.CleanArchitecture.Domain.Common.Base; - -namespace SSW.CleanArchitecture.Domain.TodoItems; - -public record TodoItemCompletedEvent(TodoItem Item) : DomainEvent; diff --git a/src/Domain/TodoItems/TodoItemCreatedEvent.cs b/src/Domain/TodoItems/TodoItemCreatedEvent.cs deleted file mode 100644 index 60af38c4..00000000 --- a/src/Domain/TodoItems/TodoItemCreatedEvent.cs +++ /dev/null @@ -1,5 +0,0 @@ -using SSW.CleanArchitecture.Domain.Common.Base; - -namespace SSW.CleanArchitecture.Domain.TodoItems; - -public record TodoItemCreatedEvent(TodoItem Item) : DomainEvent; diff --git a/src/Infrastructure/Migrations/20240529121636_Remove_Todos.Designer.cs b/src/Infrastructure/Migrations/20240529121636_Remove_Todos.Designer.cs new file mode 100644 index 00000000..20eaa0dc --- /dev/null +++ b/src/Infrastructure/Migrations/20240529121636_Remove_Todos.Designer.cs @@ -0,0 +1,185 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SSW.CleanArchitecture.Infrastructure.Persistence; + +#nullable disable + +namespace SSW.CleanArchitecture.Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240529121636_Remove_Todos")] + partial class Remove_Todos + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Heroes.Hero", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Alias") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PowerLevel") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("Heroes"); + }); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Teams.Mission", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TeamId") + .HasColumnType("uniqueidentifier"); + + b.Property("UpdatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.ToTable("Mission"); + }); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Teams.Team", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalPowerLevel") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("UpdatedBy") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Teams"); + }); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Heroes.Hero", b => + { + b.HasOne("SSW.CleanArchitecture.Domain.Teams.Team", null) + .WithMany("Heroes") + .HasForeignKey("TeamId"); + + b.OwnsMany("SSW.CleanArchitecture.Domain.Heroes.Power", "Powers", b1 => + { + b1.Property("HeroId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PowerLevel") + .HasColumnType("int"); + + b1.HasKey("HeroId", "Id"); + + b1.ToTable("Heroes"); + + b1.ToJson("Powers"); + + b1.WithOwner() + .HasForeignKey("HeroId"); + }); + + b.Navigation("Powers"); + }); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Teams.Mission", b => + { + b.HasOne("SSW.CleanArchitecture.Domain.Teams.Team", null) + .WithMany("Missions") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SSW.CleanArchitecture.Domain.Teams.Team", b => + { + b.Navigation("Heroes"); + + b.Navigation("Missions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Migrations/20240529121636_Remove_Todos.cs b/src/Infrastructure/Migrations/20240529121636_Remove_Todos.cs new file mode 100644 index 00000000..e7564a6a --- /dev/null +++ b/src/Infrastructure/Migrations/20240529121636_Remove_Todos.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SSW.CleanArchitecture.Infrastructure.Migrations +{ + /// + public partial class Remove_Todos : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoItems"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CreatedAt = table.Column(type: "datetimeoffset", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + Done = table.Column(type: "bit", nullable: false), + Note = table.Column(type: "nvarchar(max)", nullable: true), + Priority = table.Column(type: "int", nullable: false), + Reminder = table.Column(type: "datetime2", nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + UpdatedAt = table.Column(type: "datetimeoffset", nullable: true), + UpdatedBy = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + } + } +} diff --git a/src/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Infrastructure/Persistence/ApplicationDbContext.cs index 24bec7fa..702b1460 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContext.cs @@ -1,11 +1,8 @@ using Microsoft.EntityFrameworkCore; using SSW.CleanArchitecture.Application.Common.Interfaces; -using SSW.CleanArchitecture.Domain.Common.Base; using SSW.CleanArchitecture.Domain.Common.Interfaces; using SSW.CleanArchitecture.Domain.Heroes; using SSW.CleanArchitecture.Domain.Teams; -using SSW.CleanArchitecture.Domain.TodoItems; -using SSW.CleanArchitecture.Infrastructure.Persistence.Interceptors; using System.Reflection; namespace SSW.CleanArchitecture.Infrastructure.Persistence; @@ -14,8 +11,6 @@ public class ApplicationDbContext( DbContextOptions options) : DbContext(options), IApplicationDbContext { - public DbSet TodoItems => Set(); - public DbSet Heroes => AggregateRootSet(); public DbSet Teams => AggregateRootSet(); diff --git a/src/Infrastructure/Persistence/Configuration/TodoItemConfiguration.cs b/src/Infrastructure/Persistence/Configuration/TodoItemConfiguration.cs deleted file mode 100644 index ec707231..00000000 --- a/src/Infrastructure/Persistence/Configuration/TodoItemConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Infrastructure.Persistence.Configuration; - -public class TodoItemConfiguration : IEntityTypeConfiguration -{ - // TODO: Rip out the common pieces that are from BaseEntity (https://github.com/SSWConsulting/SSW.CleanArchitecture/issues/78) - // virtual method, override - // Good marker to enforce that all entities have configuration defined via arch tests - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(t => t.Id); - - builder.Property(t => t.Id) - .HasConversion(x => x.Value, - x => new TodoItemId(x)) - .ValueGeneratedOnAdd(); - - builder.Property(t => t.Title) - .HasMaxLength(200) - .IsRequired(); - } -} \ No newline at end of file diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 6323d1d6..0331bbc4 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -126,46 +126,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Teams"); }); - modelBuilder.Entity("SSW.CleanArchitecture.Domain.TodoItems.TodoItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CreatedAt") - .HasColumnType("datetimeoffset"); - - b.Property("CreatedBy") - .HasColumnType("nvarchar(max)"); - - b.Property("Done") - .HasColumnType("bit"); - - b.Property("Note") - .HasColumnType("nvarchar(max)"); - - b.Property("Priority") - .HasColumnType("int"); - - b.Property("Reminder") - .HasColumnType("datetime2"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UpdatedAt") - .HasColumnType("datetimeoffset"); - - b.Property("UpdatedBy") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TodoItems"); - }); - modelBuilder.Entity("SSW.CleanArchitecture.Domain.Heroes.Hero", b => { b.HasOne("SSW.CleanArchitecture.Domain.Teams.Team", null) diff --git a/src/WebApi/DependencyInjection.cs b/src/WebApi/DependencyInjection.cs index 08db1693..52305715 100644 --- a/src/WebApi/DependencyInjection.cs +++ b/src/WebApi/DependencyInjection.cs @@ -42,7 +42,7 @@ private static void AddHealthChecks(IServiceCollection services, IConfiguration { // TODO: Replace the custom test query below with something appropriate for your project that is always expected to be valid _ = await ctx - .TodoItems + .Heroes // allows you to understand why you might see constant db queries in sql profile .TagWith("HealthCheck") .FirstOrDefaultAsync(ct); diff --git a/src/WebApi/Features/TodoItemEndpoints.cs b/src/WebApi/Features/TodoItemEndpoints.cs deleted file mode 100644 index 1ae4f150..00000000 --- a/src/WebApi/Features/TodoItemEndpoints.cs +++ /dev/null @@ -1,32 +0,0 @@ -using MediatR; -using SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CreateTodoItem; -using SSW.CleanArchitecture.Application.Features.TodoItems.Queries.GetAllTodoItems; -using SSW.CleanArchitecture.WebApi.Extensions; - -namespace SSW.CleanArchitecture.WebApi.Features; - -public static class TodoItemEndpoints -{ - public static void MapTodoItemEndpoints(this WebApplication app) - { - var group = app - .MapGroup("todoitems") - .WithTags("TodoItems") - .WithOpenApi(); - - group - .MapGet("/", (ISender sender, CancellationToken ct) - => sender.Send(new GetAllTodoItemsQuery(), ct)) - .WithName("GetTodoItems") - .ProducesGet(); - - // TODO: Investigate examples for swagger docs. i.e. better docs than: - // myWeirdField: "string" vs myWeirdField: "this-silly-string" - // (https://github.com/SSWConsulting/SSW.CleanArchitecture/issues/79) - - group - .MapPost("/", (ISender sender, CreateTodoItemCommand command, CancellationToken ct) => sender.Send(command, ct)) - .WithName("CreateTodoItem") - .ProducesPost(); - } -} \ No newline at end of file diff --git a/src/WebApi/Program.cs b/src/WebApi/Program.cs index d38c5925..2f2dc614 100644 --- a/src/WebApi/Program.cs +++ b/src/WebApi/Program.cs @@ -40,7 +40,6 @@ app.UseDefaultExceptionHandler(); app.MapHeroEndpoints(); app.MapTeamEndpoints(); -app.MapTodoItemEndpoints(); app.Run(); diff --git a/tests/Domain.UnitTests/TodoItems/TodoItemByTitleSpecTests.cs b/tests/Domain.UnitTests/TodoItems/TodoItemByTitleSpecTests.cs deleted file mode 100644 index 4ec91c7b..00000000 --- a/tests/Domain.UnitTests/TodoItems/TodoItemByTitleSpecTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Domain.UnitTests.TodoItems; - -public class TodoItemByTitleSpecTests -{ - private readonly List _entities = - [ - TodoItem.Create("Apple"), - TodoItem.Create("Banana"), - TodoItem.Create("Apple 2"), - TodoItem.Create("Banana 2"), - TodoItem.Create("Hello world 2") - ]; - - [Theory] - [InlineData("Apple")] - [InlineData("Banana")] - [InlineData("Apple 2")] - [InlineData("Banana 2")] - public void Query_ShouldReturnByTitle(string textToSearch) - { - var query = new TodoItemByTitleSpec(textToSearch); - var result = query.Evaluate(_entities).ToList(); - - result.Count.Should().Be(1); - result.First().Title.Should().Be(textToSearch); - } -} \ No newline at end of file diff --git a/tests/Domain.UnitTests/TodoItems/TodoItemTests.cs b/tests/Domain.UnitTests/TodoItems/TodoItemTests.cs deleted file mode 100644 index dd9bab93..00000000 --- a/tests/Domain.UnitTests/TodoItems/TodoItemTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace SSW.CleanArchitecture.Domain.UnitTests.TodoItems; - -public class TodoItemTests -{ - [Fact] - public void Create_WithValidTitle_ShouldSucceed() - { - // Arrange - var title = "title"; - - // Act - var todoItem = TodoItem.Create(title); - - // Assert - todoItem.Should().NotBeNull(); - todoItem.Title.Should().Be(title); - todoItem.Priority.Should().Be(PriorityLevel.None); - } - - [Fact] - public void Create_WithNullTitle_ShouldThrow() - { - // Arrange - string? title = null; - - // Act - Action act = () => TodoItem.Create(title!); - - // Assert - act.Should().Throw().WithMessage("Value cannot be null. (Parameter 'title')"); - } - - [Fact] - public void Create_ShouldRaiseDomainEvent() - { - // Act - var todoItem = TodoItem.Create("title"); - - // Assert - todoItem.DomainEvents.Should().NotBeNull(); - todoItem.DomainEvents.Should().HaveCount(1); - todoItem.DomainEvents.Should().ContainSingle(x => x is TodoItemCreatedEvent); - } - - [Fact] - public void Complete_ShouldSetDone() - { - // Arrange - var todoItem = TodoItem.Create("title"); - - // Act - todoItem.Complete(); - - // Assert - todoItem.Done.Should().BeTrue(); - } - - [Fact] - public void Complete_ShouldRaiseDomainEvent() - { - // Arrange - var todoItem = TodoItem.Create("title"); - - // Act - todoItem.Complete(); - - // Assert - todoItem.DomainEvents.Should().NotBeNull(); - todoItem.DomainEvents.Should().HaveCount(2); - todoItem.DomainEvents.Should().ContainSingle(x => x is TodoItemCompletedEvent); - } -} \ No newline at end of file diff --git a/tests/WebApi.IntegrationTests/Common/Factories/TodoItemFactory.cs b/tests/WebApi.IntegrationTests/Common/Factories/TodoItemFactory.cs deleted file mode 100644 index 11f6be16..00000000 --- a/tests/WebApi.IntegrationTests/Common/Factories/TodoItemFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Bogus; -using SSW.CleanArchitecture.Domain.TodoItems; - -namespace WebApi.IntegrationTests.Common.Factories; - -public static class TodoItemFactory -{ - private static readonly Faker Faker = new Faker().CustomInstantiator(f => TodoItem.Create( - f.Lorem.Sentence() - )); - - public static TodoItem Generate() => Faker.Generate(); - - public static IEnumerable Generate(int num) => Faker.Generate(num); -} \ No newline at end of file diff --git a/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Commands/CreateTodoItemCommandTests.cs b/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Commands/CreateTodoItemCommandTests.cs deleted file mode 100644 index 78b4daa6..00000000 --- a/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Commands/CreateTodoItemCommandTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using SSW.CleanArchitecture.Application.Common.Exceptions; -using SSW.CleanArchitecture.Application.Features.TodoItems.Commands.CreateTodoItem; -using SSW.CleanArchitecture.Domain.TodoItems; -using System.Net; -using System.Net.Http.Json; -using WebApi.IntegrationTests.Common.Fixtures; - -namespace WebApi.IntegrationTests.Endpoints.TodoItems.Commands.CreateTodoItem; - -public class CreateTodoItemCommandTests(TestingDatabaseFixture fixture, ITestOutputHelper output) - : IntegrationTestBase(fixture, output) -{ - [Fact] - public async Task Command_ShouldRequireUniqueTitle() - { - // Arrange - var cmd = new CreateTodoItemCommand("Shopping"); - var client = GetAnonymousClient(); - var createTodoItem = async () => await client.PostAsJsonAsync("/todoitems", cmd); - await createTodoItem(); - - // Act - var result = await createTodoItem(); - var validation = await result.Content.ReadFromJsonAsync(); - - // Assert - result.StatusCode.Should().Be(HttpStatusCode.BadRequest); - validation.Should().NotBeNull(); - validation!.Errors.Should().HaveCount(1); - } - - [Fact] - public async Task Command_ShouldCreateTodoItem() - { - // Arrange - var cmd = new CreateTodoItemCommand("Shopping"); - var client = GetAnonymousClient(); - - // Act - var result = await client.PostAsJsonAsync("/todoitems", cmd); - - // Assert - var item = await Context.TodoItems.FirstOrDefaultAsync(t => t.Title == cmd.Title); - - item.Should().NotBeNull(); - item!.Title.Should().Be(cmd.Title); - item.CreatedAt.Should().BeCloseTo(DateTime.Now, TimeSpan.FromSeconds(10)); - } -} \ No newline at end of file diff --git a/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Queries/GetAllTodoItemsQueryTests.cs b/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Queries/GetAllTodoItemsQueryTests.cs deleted file mode 100644 index a9ea0705..00000000 --- a/tests/WebApi.IntegrationTests/Endpoints/TodoItems/Queries/GetAllTodoItemsQueryTests.cs +++ /dev/null @@ -1,28 +0,0 @@ -using NSubstitute.Core; -using SSW.CleanArchitecture.Application.Features.TodoItems.Queries.GetAllTodoItems; -using System.Net.Http.Json; -using WebApi.IntegrationTests.Common.Factories; -using WebApi.IntegrationTests.Common.Fixtures; - -namespace WebApi.IntegrationTests.Endpoints.TodoItems.Queries.GetAllTodoItems; - -public class GetAllTodoItemsQueryTests(TestingDatabaseFixture fixture, ITestOutputHelper output) - : IntegrationTestBase(fixture, output) -{ - [Fact] - public async Task Query_ShouldReturnAllTodoItems() - { - // Arrange - const int entityCount = 10; - var entities = TodoItemFactory.Generate(entityCount); - await AddEntitiesAsync(entities); - var client = GetAnonymousClient(); - - // Act - var result = await client.GetFromJsonAsync("/todoitems"); - - // Assert - result.Should().NotBeNull(); - result!.Length.Should().Be(entityCount); - } -} \ No newline at end of file diff --git a/tools/Database/ApplicationDbContextInitializer.cs b/tools/Database/ApplicationDbContextInitializer.cs index 4f6d1f57..7cc2e1c0 100644 --- a/tools/Database/ApplicationDbContextInitializer.cs +++ b/tools/Database/ApplicationDbContextInitializer.cs @@ -16,7 +16,8 @@ public class ApplicationDbContextInitializer( "Superman", "Batman", "Wonder Woman", - "Flash", "Aquaman", + "Flash", + "Aquaman", "Cyborg", "Green Lantern", "Shazam", @@ -128,7 +129,7 @@ private async Task SeedTeams(List heroes) { var name = f.PickRandom(_teamNames); var team = Team.Create(name); - var heroesToAdd = f.PickRandom(heroes, f.Random.Number(2, 5)); + var heroesToAdd = f.PickRandom(heroes, f.Random.Number(1, 3)); foreach (var hero in heroesToAdd) team.AddHero(hero);