diff --git a/.template.config/dotnetcli.host.json b/.template.config/dotnetcli.host.json
index 9ab3548..9aac2e0 100644
--- a/.template.config/dotnetcli.host.json
+++ b/.template.config/dotnetcli.host.json
@@ -2,6 +2,12 @@
"symbolInfo": {
"UseOracle": {
"longName": "use-oracle"
+ },
+ "EnableContainerSupport": {
+ "longName": "enable-container-support"
+ },
+ "UseController": {
+ "longName": "use-controller"
}
}
}
\ No newline at end of file
diff --git a/.template.config/ide.host.json b/.template.config/ide.host.json
index 9405d54..eb6e3e9 100644
--- a/.template.config/ide.host.json
+++ b/.template.config/ide.host.json
@@ -6,12 +6,28 @@
{
"id": "UseOracle",
"name": {
- "text": "Use Oracle"
+ "text": "Use oracle"
},
"description": {
- "text": "Use Oracle for database (default is LocalDB)."
+ "text": "Use oracle for database (default is LocalDB)."
},
"isVisible": true
+ },
+ {
+ "id": "EnableContainerSupport",
+ "name": {
+ "text": "Enable container support"
+ },
+ "description": {
+ "text": "Enable container support and add Docker file for linux."
+ },
+ "isVisible": true
+ },
+ {
+ "id": "UseController",
+ "name": {
+ "text": "Use controller instead of minimals APIs."
+ }
}
]
}
\ No newline at end of file
diff --git a/.template.config/template.json b/.template.config/template.json
index 00940e3..053a712 100644
--- a/.template.config/template.json
+++ b/.template.config/template.json
@@ -31,6 +31,18 @@
"type": "computed",
"value": "(!UseOracle)"
},
+ "UseController": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "true",
+ "description": "Use controller instead of minimals APIs."
+ },
+ "EnableContainerSupport": {
+ "type": "parameter",
+ "datatype": "bool",
+ "defaultValue": "false",
+ "description": "Enable container support (Docker for linux)."
+ },
"fcaRepositoryUrl": {
"type": "generated",
"generator": "constant",
@@ -44,7 +56,7 @@
"generator": "constant",
"replaces": "fcaPackageVersion",
"parameters": {
- "value": "8.0.8"
+ "value": "8.0.1"
}
}
},
@@ -93,6 +105,19 @@
"appsettings.Oracle.json": "appsettings.json",
"TestDatabase.Oracle.cs": "TestDatabase.cs"
}
+ },
+ {
+ "condition": "(!UseController)",
+ "exclude": [
+ "src/API/Controllers"
+ ]
+ },
+ {
+ "condition": "(!EnableContainerSupport)",
+ "exclude": [
+ "src/API/Dockerfile",
+ ".dockerignore"
+ ]
}
]
}
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..ecd91fe
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+ net8.0
+ true
+ enable
+ enable
+
+
\ No newline at end of file
diff --git a/FastCleanArchitecture.nuspec b/FastCleanArchitecture.nuspec
index 64f62c3..2926f76 100644
--- a/FastCleanArchitecture.nuspec
+++ b/FastCleanArchitecture.nuspec
@@ -1,33 +1,35 @@
-
+
- Fast.Clean.Architecture.Solution.Template
- 8.0.1
- Fast Clean Architecture Solution Template
- christiandr
- Fast Clean Architecture Solution Template for .NET 8.
-
- A Clean Architecture Solution Template for creating apps using Web API only with DotNet.
-
+ Fast.Clean.Architecture.Solution.Template
+ 8.0.1
+ Fast Clean Architecture Solution Template
+ christiandr
+ Fast Clean Architecture Solution Template for .NET 8.
+
+ A Clean Architecture Solution Template for create apps.
+
+
+
- https://github.com/christianrd/FastCleanArchitecture
-
+ https://github.com/christianrd/FastCleanArchitecture
+
- MIT
- false
- fast-clean-architecture clean-architecture project template csharp dotnet
- icon.png
- README.md
+ MIT
+ false
+ fast-clean-architecture clean-architecture project template csharp dotnet
+ icon.png
+ README.md
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index d9b0a7f..2107491 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,6 @@ Starting is quick and easy-just install the .NET template (detailed instructions
The following prerequisites are required to build and run the solution:
- [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (latest version)
-- [Node.js](https://nodejs.org/) (latest LTS, only required if you are using Angular or React)
The easiest way to get started is to install the [.NET template](https://www.nuget.org/packages/Fast.Clean.Architecture.Solution.Template):
```
@@ -85,6 +84,8 @@ The template includes a full CI/CD pipeline. The pipeline is responsible for bui
* [ASP.NET Core 8](https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core)
* [Entity Framework Core 8](https://docs.microsoft.com/en-us/ef/core/)
* [MediatR](https://github.com/jbogard/MediatR)
+* [Maspter](https://github.com/MapsterMapper/Mapster)
+* [FluentResult](https://github.com/altmann/FluentResults)
* [FluentValidation](https://fluentvalidation.net/)
* [NUnit](https://nunit.org/), [FluentAssertions](https://fluentassertions.com/), [Moq](https://github.com/devlooped/moq) & [Respawn](https://github.com/jbogard/Respawn)
diff --git a/fastCleanArchitecture.sln b/fastCleanArchitecture.sln
index 3a96ac0..b47c5bb 100644
--- a/fastCleanArchitecture.sln
+++ b/fastCleanArchitecture.sln
@@ -19,10 +19,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infra
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "src\API\API.csproj", "{3A7EAD0E-2F46-4022-9119-96593FFEC72D}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C7FBE7FF-00ED-4E1B-B015-CC61AE8E0FD3}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C7FBE7FF-00ED-4E1B-B015-CC61AE8E0FD3}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
FastCleanArchitecture.nuspec = FastCleanArchitecture.nuspec
+ README-template.md = README-template.md
README.md = README.md
EndProjectSection
EndProject
diff --git a/src/API/API.csproj b/src/API/API.csproj
index 77031d3..cb385fc 100644
--- a/src/API/API.csproj
+++ b/src/API/API.csproj
@@ -5,8 +5,10 @@
enable
enable
f5e32e1e-4b42-4557-9635-b9a0c84f3591
+
Linux
..\..
+
FastCleanArchitecture
$(CompanyName)
$(CompanyName.Replace(" ", "")).$(MSBuildProjectName.Replace(" ", ""))
@@ -14,7 +16,9 @@
+
+
all
diff --git a/src/API/Controllers/BaseController.cs b/src/API/Controllers/ApiControllerBase.cs
similarity index 55%
rename from src/API/Controllers/BaseController.cs
rename to src/API/Controllers/ApiControllerBase.cs
index e01a552..742241c 100644
--- a/src/API/Controllers/BaseController.cs
+++ b/src/API/Controllers/ApiControllerBase.cs
@@ -4,9 +4,9 @@
namespace FastCleanArchitecture.API.Controllers;
[ApiController]
-public abstract class BaseController : ControllerBase
+public abstract class ApiControllerBase : ControllerBase
{
protected readonly ISender Sender;
- public BaseController(ISender Sender) => this.Sender = Sender;
-}
\ No newline at end of file
+ public ApiControllerBase(ISender Sender) => this.Sender = Sender;
+}
diff --git a/src/API/Controllers/TodoItems/TodoItemsController.cs b/src/API/Controllers/TodoItems/TodoItemsController.cs
new file mode 100644
index 0000000..7704de1
--- /dev/null
+++ b/src/API/Controllers/TodoItems/TodoItemsController.cs
@@ -0,0 +1,36 @@
+using FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
+using FastCleanArchitecture.Application.TodoItems.Commands.DeleteTodoItem;
+using FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItem;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FastCleanArchitecture.API.Controllers.TodoItems;
+
+[Route("api/[controller]")]
+public sealed class TodoItemsController(ISender sender) : ApiControllerBase(sender)
+{
+ [HttpPost]
+ public async Task CreateTodoItem([FromBody] CreateTodoItemCommand request)
+ {
+ await Sender.Send(request);
+
+ return NoContent();
+ }
+
+ [HttpPut("{Id}")]
+ public async Task UpdateTodoItem(UpdateTodoItemCommand request)
+ {
+ var result = await Sender.Send(request);
+ if (result.IsFailed)
+ return NotFound("Item not found.");
+
+ return NoContent();
+ }
+
+ [HttpDelete("{Id}")]
+ public async Task DeleteTodoItem([FromRoute] DeleteTodoItemCommand request)
+ {
+ await Sender.Send(request);
+ return NoContent();
+ }
+}
diff --git a/src/API/Controllers/TodoLists/TodoListsController.cs b/src/API/Controllers/TodoLists/TodoListsController.cs
new file mode 100644
index 0000000..26bb0c1
--- /dev/null
+++ b/src/API/Controllers/TodoLists/TodoListsController.cs
@@ -0,0 +1,46 @@
+using FastCleanArchitecture.Application.TodoLists.Commands.Create;
+using FastCleanArchitecture.Application.TodoLists.Commands.Delete;
+using FastCleanArchitecture.Application.TodoLists.Commands.Update;
+using FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FastCleanArchitecture.API.Controllers.TodoLists;
+
+[Route("api/[controller]")]
+public sealed class TodoListsController(ISender Sender) : ApiControllerBase(Sender)
+{
+ [HttpGet(Name = nameof(GetTodoLists))]
+ public async Task GetTodoLists()
+ {
+ var result = await Sender.Send(new GetTodosQuery());
+ return Ok(result.ValueOrDefault);
+ }
+
+ [HttpPost]
+ public async Task CreateTodoList(CreateTodoListCommand request)
+ {
+ var result = await Sender.Send(request);
+ if (result.IsFailed)
+ return BadRequest(result.Errors);
+
+ return CreatedAtRoute(nameof(GetTodoLists), result.Value);
+ }
+
+ [HttpPut("{Id}")]
+ public async Task UpdateTodoList(UpdateTodoListCommand request)
+ {
+ var result = await Sender.Send(request);
+ if (result.IsFailed)
+ return BadRequest(result.Errors);
+
+ return NoContent();
+ }
+
+ [HttpDelete("{Id}")]
+ public async Task DeleteTodoList([FromRoute] DeleteTodoListCommand request)
+ {
+ await Sender.Send(request);
+ return NoContent();
+ }
+}
diff --git a/src/API/Controllers/TodoListsController.cs b/src/API/Controllers/TodoListsController.cs
deleted file mode 100644
index 0b5c2d0..0000000
--- a/src/API/Controllers/TodoListsController.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using FastCleanArchitecture.Application.TodoLists.Commands.CreatTodoList;
-using MediatR;
-using Microsoft.AspNetCore.Mvc;
-
-namespace FastCleanArchitecture.API.Controllers;
-
-[Route("api/[controller]")]
-public sealed class TodoListsController(ISender Sender) : BaseController(Sender)
-{
- [HttpPost]
- public async Task CreateTodoList(CreateTodoListCommand request)
- {
- var result = await Sender.Send(request);
- if (result.IsFailed)
- return BadRequest(result.Errors);
-
- return NoContent();
- }
-}
diff --git a/src/API/Program.cs b/src/API/Program.cs
index 672354a..a03a876 100644
--- a/src/API/Program.cs
+++ b/src/API/Program.cs
@@ -2,8 +2,9 @@
using FastCleanArchitecture.Infrastructure;
var builder = WebApplication.CreateBuilder(args);
-
+#if (UseController)
builder.Services.AddControllers();
+#endif
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
@@ -22,8 +23,10 @@
app.UseHttpsRedirection();
+#if (UseController)
app.MapControllers();
+#endif
await app.UseInfrastructureAsync();
-app.Run();
\ No newline at end of file
+app.Run();
diff --git a/src/Application/.templating.config/dotnetcli.host.json b/src/Application/.templating.config/dotnetcli.host.json
new file mode 100644
index 0000000..d4447a8
--- /dev/null
+++ b/src/Application/.templating.config/dotnetcli.host.json
@@ -0,0 +1,16 @@
+{
+ "symbolInfo": {
+ "featureName": {
+ "longName": "feature-name",
+ "shortName": "fn"
+ },
+ "returnType": {
+ "longName": "return-type",
+ "shortName": "rt"
+ },
+ "useCaseType": {
+ "longName": "usecase-type",
+ "shortName": "ut"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Application/.templating.config/icon.png b/src/Application/.templating.config/icon.png
new file mode 100644
index 0000000..68e954e
Binary files /dev/null and b/src/Application/.templating.config/icon.png differ
diff --git a/src/Application/.templating.config/template.json b/src/Application/.templating.config/template.json
new file mode 100644
index 0000000..3635e40
--- /dev/null
+++ b/src/Application/.templating.config/template.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "christiandr",
+ "classifications": [
+ "Fast Clean Architecture"
+ ],
+ "name": "Fast Clean Architecture Solution Use Case",
+ "description": "Create a new use case (query or command)",
+ "identity": "Fast.Clean.Architecture.Solution.UseCase.CSharp",
+ "groupIdentity": "Fast.Clean.Architecture.Solution.UseCase",
+ "shortName": "fast-ca-usecase",
+ "tags": {
+ "language": "C#",
+ "type": "item"
+ },
+ "sourceName": "UpdateTodoItemDetail",
+ "preferNameDirectory": false,
+ "symbols": {
+ "DefaultNamespace": {
+ "type": "bind",
+ "binding": "msbuild:RootNamespace",
+ "replaces": "FastCleanArchitecture.Application",
+ "defaultValue": "FastCleanArchitecture.Application"
+ },
+ "featureName": {
+ "type": "parameter",
+ "datatype": "string",
+ "isRequired": true,
+ "replaces": "TodoItems",
+ "fileRename": "TodoItems"
+ },
+ "useCaseType": {
+ "type": "parameter",
+ "datatype": "choice",
+ "isRequired": true,
+ "choices": [
+ {
+ "choice": "command",
+ "description": "Create a new command"
+ },
+ {
+ "choice": "query",
+ "description": "Create a new query"
+ }
+ ],
+ "description": "The type of use case to create"
+ },
+ "createCommand": {
+ "type": "computed",
+ "value": "(useCaseType == \"command\")"
+ },
+ "createQuery": {
+ "type": "computed",
+ "value": "(useCaseType == \"query\")"
+ },
+ "returnType": {
+ "type": "parameter",
+ "datatype": "string",
+ "isRequired": false,
+ "replaces": "object",
+ "defaultValue": "object"
+ }
+ },
+ "sources": [
+ {
+ "modifiers": [
+ {
+ "condition": "(createCommand)",
+ "exclude": [ "TodoItems/Queries/**/*" ]
+ },
+ {
+ "condition": "(createQuery)",
+ "exclude": [ "TodoItems/Commands/**/*" ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj
index ba890d0..95ab0f7 100644
--- a/src/Application/Application.csproj
+++ b/src/Application/Application.csproj
@@ -12,7 +12,10 @@
-
+
+
+
+
diff --git a/src/Application/Common/Behaviors/LoggingBehavior.cs b/src/Application/Common/Behaviors/LoggingBehavior.cs
index 4f25596..4bfdac3 100644
--- a/src/Application/Common/Behaviors/LoggingBehavior.cs
+++ b/src/Application/Common/Behaviors/LoggingBehavior.cs
@@ -20,18 +20,18 @@ public async Task Handle(TRequest request, RequestHandlerDelegate : IPipelineBehavior where TRequest : notnull
+{
+ private readonly Stopwatch _timer;
+ private readonly ILogger _logger;
+
+ public PerformanceBehavior(Stopwatch timer, ILogger logger)
+ {
+ _timer = timer;
+ _logger = logger;
+ }
+
+ public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken)
+ {
+ _timer.Start();
+
+ var response = await next();
+
+ _timer.Stop();
+
+ var elapsedMilliseconds = _timer.ElapsedMilliseconds;
+
+ if (elapsedMilliseconds > 500)
+ {
+ var requestName = typeof(TRequest).Name;
+
+ _logger.LogWarning("FastCleanArchitecture Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@Request}",
+ requestName, elapsedMilliseconds, request);
+ }
+
+ return response;
+ }
+}
diff --git a/src/Application/Common/Behaviors/ValidationBehavior.cs b/src/Application/Common/Behaviors/ValidationBehavior.cs
index 2161066..aaa84b7 100644
--- a/src/Application/Common/Behaviors/ValidationBehavior.cs
+++ b/src/Application/Common/Behaviors/ValidationBehavior.cs
@@ -21,10 +21,11 @@ public async Task Handle(TRequest request, RequestHandlerDelegate(request);
+ var validationResults = await Task.WhenAll(
+ _validators.Select(v => v.ValidateAsync(context, cancellationToken)));
- var validationErrors = _validators
- .Select(validator => validator.Validate(context))
- .Where(validationResult => validationResult.Errors.Any())
+ var validationErrors = validationResults
+ .Where(vr => vr.Errors.Any())
.SelectMany(validationResult => validationResult.Errors)
.Select(validationFailure => new ValidationError(
validationFailure.PropertyName,
@@ -36,4 +37,4 @@ public async Task Handle(TRequest request, RequestHandlerDelegate.NewConfig()
+ .Map(dest => dest.Priority, src => (int)src.Priority);
+
+ TypeAdapterConfig.NewConfig();
+ TypeAdapterConfig.NewConfig();
+ }
+}
diff --git a/src/Application/Common/Models/LookupDto.cs b/src/Application/Common/Models/LookupDto.cs
new file mode 100644
index 0000000..df48646
--- /dev/null
+++ b/src/Application/Common/Models/LookupDto.cs
@@ -0,0 +1,7 @@
+namespace FastCleanArchitecture.Application.Common.Models;
+
+public sealed class LookupDto
+{
+ public int Id { get; set; }
+ public string? Title { get; set; }
+}
diff --git a/src/Application/DependencyInjection.cs b/src/Application/DependencyInjection.cs
index 03217dc..472200e 100644
--- a/src/Application/DependencyInjection.cs
+++ b/src/Application/DependencyInjection.cs
@@ -1,5 +1,8 @@
using FastCleanArchitecture.Application.Common.Behaviors;
+using FastCleanArchitecture.Application.Common.Mappings;
using FluentValidation;
+using Mapster;
+using MediatR;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
@@ -15,9 +18,14 @@ public static IServiceCollection AddApplication(this IServiceCollection services
conf.RegisterServicesFromAssembly(typeof(DependencyInjection).Assembly);
conf.AddOpenBehavior(typeof(LoggingBehavior<,>));
conf.AddOpenBehavior(typeof(ValidationBehavior<,>));
+ conf.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehavior<,>));
});
+ services.AddMapster();
+ MapsterConfig.Configure();
+
services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);
+
return services;
}
-}
\ No newline at end of file
+}
diff --git a/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs b/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs
new file mode 100644
index 0000000..c22c679
--- /dev/null
+++ b/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItem.cs
@@ -0,0 +1,34 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoItems;
+using FluentResults;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
+
+public record CreateTodoItemCommand : ICommand
+{
+ public Guid ListId { get; set; }
+ public string? Title { get; set; }
+}
+
+internal sealed class CreateTodoItemCommandCommandHandler : ICommandHandler
+{
+ private readonly ITodoItemRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public CreateTodoItemCommandCommandHandler(ITodoItemRepository repository, IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
+ {
+ var entity = TodoItem.Create(request.ListId, request.Title);
+
+ _repository.Add(entity);
+ await _unitOfWork.SaveChangesAsync();
+
+ return entity.Id;
+ }
+}
diff --git a/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs b/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs
new file mode 100644
index 0000000..bf32b6b
--- /dev/null
+++ b/src/Application/TodoItems/Commands/CreateTodoItem/CreateTodoItemCommandValidator.cs
@@ -0,0 +1,13 @@
+using FluentValidation;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.CreateTodoItem;
+
+public class CreateTodoItemCommandValidator : AbstractValidator
+{
+ public CreateTodoItemCommandValidator()
+ {
+ RuleFor(v => v.Title)
+ .MaximumLength(200)
+ .NotEmpty();
+ }
+}
diff --git a/src/Application/TodoItems/Commands/DeleteTodoItem/DeleteTodoItem.cs b/src/Application/TodoItems/Commands/DeleteTodoItem/DeleteTodoItem.cs
new file mode 100644
index 0000000..0148cf4
--- /dev/null
+++ b/src/Application/TodoItems/Commands/DeleteTodoItem/DeleteTodoItem.cs
@@ -0,0 +1,34 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoItems;
+using FluentResults;
+using MediatR;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.DeleteTodoItem;
+
+public record DeleteTodoItemCommand(Guid Id) : ICommand;
+
+internal sealed class DeleteTodoItemCommandHandler : ICommandHandler
+{
+ private readonly ITodoItemRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public DeleteTodoItemCommandHandler(ITodoItemRepository repository, IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
+ {
+ var entity = await _repository.GetByIdAsync(request.Id, cancellationToken);
+
+ if (entity is null)
+ return Result.Ok();
+
+ _repository.Remove(entity);
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItem.cs b/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItem.cs
new file mode 100644
index 0000000..d6b8e17
--- /dev/null
+++ b/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItem.cs
@@ -0,0 +1,46 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoItems;
+using FluentResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItem;
+
+public sealed record UpdateTodoItemCommand : ICommand
+{
+ [FromRoute]
+ public Guid Id { get; init; }
+
+ [FromBody]
+ public BodyItemRequest Body { get; set; } = new BodyItemRequest();
+
+ public record BodyItemRequest
+ {
+ public string? Title { get; init; }
+ public bool Done { get; init; }
+ }
+}
+
+internal sealed class UpdateTodoItemCommandHandler : ICommandHandler
+{
+ private readonly ITodoItemRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public UpdateTodoItemCommandHandler(ITodoItemRepository repository, IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
+ {
+ var item = await _repository.GetByIdAsync(request.Id, cancellationToken);
+
+ if (item is null) return Result.Fail("Item not found.");
+
+ _repository.Update(TodoItem.Update(request.Body.Title, request.Body.Done, item));
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandValidator.cs b/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandValidator.cs
new file mode 100644
index 0000000..3d8df2b
--- /dev/null
+++ b/src/Application/TodoItems/Commands/UpdateTodoItem/UpdateTodoItemCommandValidator.cs
@@ -0,0 +1,14 @@
+using FluentValidation;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItem;
+
+public class UpdateTodoItemCommandValidator : AbstractValidator
+{
+ public UpdateTodoItemCommandValidator()
+ {
+ RuleFor(v => v.Body.Title)
+ .MaximumLength(200)
+ .NotEmpty()
+ .OverridePropertyName(x => x.Body.Title);
+ }
+}
diff --git a/src/Application/TodoItems/Commands/UpdateTodoItemDetail/UpdateTodoItemDetail.cs b/src/Application/TodoItems/Commands/UpdateTodoItemDetail/UpdateTodoItemDetail.cs
new file mode 100644
index 0000000..d11d6be
--- /dev/null
+++ b/src/Application/TodoItems/Commands/UpdateTodoItemDetail/UpdateTodoItemDetail.cs
@@ -0,0 +1,51 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoItems;
+using FastCleanArchitecture.Domain.TodoItems.Enums;
+using FluentResults;
+using Microsoft.AspNetCore.Mvc;
+using static FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItem.UpdateTodoItemCommand;
+
+namespace FastCleanArchitecture.Application.TodoItems.Commands.UpdateTodoItemDetail;
+
+public sealed record UpdateTodoItemDetailCommand : ICommand
+{
+ [FromRoute]
+ public Guid Id { get; init; }
+
+ [FromBody]
+ public BodyItemDetailRequest Body { get; set; } = new BodyItemDetailRequest();
+
+ public record BodyItemDetailRequest
+ {
+ public Guid ListId { get; init; }
+
+ public PriorityLevel Priority { get; init; }
+
+ public string? Note { get; init; }
+ }
+}
+
+internal sealed class UpdateTodoItemDetailCommandHandler : ICommandHandler
+{
+ private readonly ITodoItemRepository _todoItemRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public UpdateTodoItemDetailCommandHandler(ITodoItemRepository todoItemRepository, IUnitOfWork unitOfWork)
+ {
+ _todoItemRepository = todoItemRepository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(UpdateTodoItemDetailCommand request, CancellationToken cancellationToken)
+ {
+ var item = await _todoItemRepository.GetByIdAsync(request.Id, cancellationToken);
+
+ if (item is null) return Result.Fail("Item not found.");
+
+ _todoItemRepository.Update(TodoItem.UpdateDetail(request.Body.ListId, request.Body.Priority, request.Body.Note, item));
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs b/src/Application/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs
new file mode 100644
index 0000000..e0d3b82
--- /dev/null
+++ b/src/Application/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs
@@ -0,0 +1,15 @@
+using FastCleanArchitecture.Domain.TodoItems.Events;
+using MediatR;
+using Microsoft.Extensions.Logging;
+
+namespace FastCleanArchitecture.Application.TodoItems.EventHandlers;
+
+internal sealed class TodoItemCreatedEventHandler(ILogger logger)
+ : INotificationHandler
+{
+ public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("FastCleanAchitecture Domain Event: {DomainEvent}", notification.GetType().Name);
+ return Task.CompletedTask;
+ }
+}
diff --git a/src/Application/TodoLists/Commands/CreatTodoList/CreateTodoList.cs b/src/Application/TodoLists/Commands/Create/CreateTodoList.cs
similarity index 98%
rename from src/Application/TodoLists/Commands/CreatTodoList/CreateTodoList.cs
rename to src/Application/TodoLists/Commands/Create/CreateTodoList.cs
index abc7bc0..5f8b825 100644
--- a/src/Application/TodoLists/Commands/CreatTodoList/CreateTodoList.cs
+++ b/src/Application/TodoLists/Commands/Create/CreateTodoList.cs
@@ -3,7 +3,7 @@
using FastCleanArchitecture.Domain.TodoLists;
using FluentResults;
-namespace FastCleanArchitecture.Application.TodoLists.Commands.CreatTodoList;
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Create;
public record CreateTodoListCommand : ICommand
{
diff --git a/src/Application/TodoLists/Commands/CreatTodoList/CreateTodoListCommandValidator.cs b/src/Application/TodoLists/Commands/Create/CreateTodoListCommandValidator.cs
similarity index 94%
rename from src/Application/TodoLists/Commands/CreatTodoList/CreateTodoListCommandValidator.cs
rename to src/Application/TodoLists/Commands/Create/CreateTodoListCommandValidator.cs
index 9fff607..bbef5fc 100644
--- a/src/Application/TodoLists/Commands/CreatTodoList/CreateTodoListCommandValidator.cs
+++ b/src/Application/TodoLists/Commands/Create/CreateTodoListCommandValidator.cs
@@ -1,7 +1,7 @@
using FastCleanArchitecture.Domain.TodoLists;
using FluentValidation;
-namespace FastCleanArchitecture.Application.TodoLists.Commands.CreatTodoList;
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Create;
public class CreateTodoListCommandValidator : AbstractValidator
{
@@ -23,6 +23,6 @@ public async Task BeUniqueTitle(string title, CancellationToken cancellati
{
var entity = await _todoListRepository.GetByTitleAsync(title, cancellationToken);
- return entity is not null;
+ return entity is null;
}
-}
\ No newline at end of file
+}
diff --git a/src/Application/TodoLists/Commands/Delete/DeleteTodoList.cs b/src/Application/TodoLists/Commands/Delete/DeleteTodoList.cs
new file mode 100644
index 0000000..42d1c65
--- /dev/null
+++ b/src/Application/TodoLists/Commands/Delete/DeleteTodoList.cs
@@ -0,0 +1,32 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoLists;
+using FluentResults;
+
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Delete;
+
+public record DeleteTodoListCommand(Guid Id) : ICommand;
+
+internal sealed class DeleteTodoListCommandHandler : ICommandHandler
+{
+ private readonly ITodoListRepository _todoListRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public DeleteTodoListCommandHandler(ITodoListRepository todoListRepository, IUnitOfWork unitOfWork)
+ {
+ _todoListRepository = todoListRepository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
+ {
+ var entity = await _todoListRepository.GetByIdAsync(request.Id, cancellationToken);
+ if (entity is null)
+ return Result.Ok();
+
+ _todoListRepository.Remove(entity);
+ await _unitOfWork.SaveChangesAsync();
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoLists/Commands/Purge/PurgeTodoLists.cs b/src/Application/TodoLists/Commands/Purge/PurgeTodoLists.cs
new file mode 100644
index 0000000..c0934e9
--- /dev/null
+++ b/src/Application/TodoLists/Commands/Purge/PurgeTodoLists.cs
@@ -0,0 +1,29 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoLists;
+using FluentResults;
+
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Purge;
+
+public record PurgeTodoListsCommand : ICommand;
+
+internal sealed class PurgeTodoListsCommandHandler : ICommandHandler
+{
+ private readonly ITodoListRepository _todoListRepository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public PurgeTodoListsCommandHandler(ITodoListRepository todoListRepository, IUnitOfWork unitOfWork)
+ {
+ _todoListRepository = todoListRepository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(PurgeTodoListsCommand request, CancellationToken cancellationToken)
+ {
+ _todoListRepository.RemoveRange(await _todoListRepository.GetAllAsync(cancellationToken));
+
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoLists/Commands/Update/UpdateTodoList.cs b/src/Application/TodoLists/Commands/Update/UpdateTodoList.cs
new file mode 100644
index 0000000..b3460a5
--- /dev/null
+++ b/src/Application/TodoLists/Commands/Update/UpdateTodoList.cs
@@ -0,0 +1,45 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoLists;
+using FluentResults;
+using Microsoft.AspNetCore.Mvc;
+
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Update;
+
+public record UpdateTodoListCommand : ICommand
+{
+ [FromRoute]
+ public Guid Id { get; init; }
+
+ [FromBody]
+ public BodyListRequest Body { get; set; } = new BodyListRequest();
+
+ public record BodyListRequest
+ {
+ public string? Title { get; init; }
+ }
+}
+
+internal sealed class UpdateTodoListCommandHandler : ICommandHandler
+{
+ private readonly ITodoListRepository _repository;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public UpdateTodoListCommandHandler(ITodoListRepository repository, IUnitOfWork unitOfWork)
+ {
+ _repository = repository;
+ _unitOfWork = unitOfWork;
+ }
+
+ public async Task Handle(UpdateTodoListCommand request, CancellationToken cancellationToken)
+ {
+ var entity = await _repository.GetByIdAsync(request.Id, cancellationToken);
+ if (entity is null)
+ return Result.Fail("Todo list not found.");
+
+ _repository.Update(TodoList.UpdateTitle(request.Body.Title!, entity));
+ await _unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return Result.Ok();
+ }
+}
diff --git a/src/Application/TodoLists/Commands/Update/UpdateTodoListCommandValidator.cs b/src/Application/TodoLists/Commands/Update/UpdateTodoListCommandValidator.cs
new file mode 100644
index 0000000..3d27624
--- /dev/null
+++ b/src/Application/TodoLists/Commands/Update/UpdateTodoListCommandValidator.cs
@@ -0,0 +1,28 @@
+using FastCleanArchitecture.Domain.TodoLists;
+using FluentValidation;
+
+namespace FastCleanArchitecture.Application.TodoLists.Commands.Update;
+
+public class UpdateTodoListCommandValidator : AbstractValidator
+{
+ private readonly ITodoListRepository _todoListRepository;
+
+ public UpdateTodoListCommandValidator(ITodoListRepository todoListRepository)
+ {
+ _todoListRepository = todoListRepository;
+
+ RuleFor(v => v.Body.Title)
+ .NotEmpty()
+ .MaximumLength(200)
+ .MustAsync(BeUniqueTitle)
+ .WithMessage("'{PropertyName}' must be unique.")
+ .WithErrorCode("Unique")
+ .OverridePropertyName(x => x.Body.Title);
+ }
+
+ public async Task BeUniqueTitle(UpdateTodoListCommand request, string title, CancellationToken cancellationToken)
+ {
+ var entity = await _todoListRepository.GetByIdAsync(request.Id, cancellationToken);
+ return entity is not null && entity!.Title!.Equals(title, StringComparison.OrdinalIgnoreCase);
+ }
+}
diff --git a/src/Application/TodoLists/Queries/GetTodos/GetTodos.cs b/src/Application/TodoLists/Queries/GetTodos/GetTodos.cs
new file mode 100644
index 0000000..441ac50
--- /dev/null
+++ b/src/Application/TodoLists/Queries/GetTodos/GetTodos.cs
@@ -0,0 +1,28 @@
+using FastCleanArchitecture.Application.Common.Messaging;
+using FastCleanArchitecture.Application.Common.Models;
+using FastCleanArchitecture.Domain.TodoItems.Enums;
+using FastCleanArchitecture.Domain.TodoLists;
+using FluentResults;
+using Mapster;
+
+namespace FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
+
+public record GetTodosQuery : IQuery;
+
+internal sealed class GetTodosQueryHandler(ITodoListRepository todoListRepository) : IQueryHandler
+{
+ public async Task> Handle(GetTodosQuery request, CancellationToken cancellationToken)
+ {
+ var todoLists = await todoListRepository.GetAllAsync(cancellationToken);
+
+ return new TodosVm
+ {
+ PriorityLevels = Enum.GetValues(typeof(PriorityLevel))
+ .Cast()
+ .Select(priorityLevel => new LookupDto { Id = (int)priorityLevel, Title = priorityLevel.ToString() })
+ .ToList(),
+
+ Lists = todoLists.Adapt>()
+ };
+ }
+}
diff --git a/src/Application/TodoLists/Queries/GetTodos/TodoItemDto.cs b/src/Application/TodoLists/Queries/GetTodos/TodoItemDto.cs
new file mode 100644
index 0000000..8864311
--- /dev/null
+++ b/src/Application/TodoLists/Queries/GetTodos/TodoItemDto.cs
@@ -0,0 +1,10 @@
+namespace FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
+
+public sealed class TodoItemDto
+{
+ public Guid Id { get; set; }
+ public string? Title { get; set; }
+ public bool Done { get; set; }
+ public int Priority { get; set; }
+ public string? Note { get; set; }
+}
diff --git a/src/Application/TodoLists/Queries/GetTodos/TodoListDto.cs b/src/Application/TodoLists/Queries/GetTodos/TodoListDto.cs
new file mode 100644
index 0000000..c2fb6c5
--- /dev/null
+++ b/src/Application/TodoLists/Queries/GetTodos/TodoListDto.cs
@@ -0,0 +1,12 @@
+namespace FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
+
+public sealed class TodoListDto
+{
+ public Guid Id { get; set; }
+
+ public string? Title { get; set; }
+
+ public string? Colour { get; set; }
+
+ public IReadOnlyCollection Items { get; set; } = [];
+}
diff --git a/src/Application/TodoLists/Queries/GetTodos/TodosVm.cs b/src/Application/TodoLists/Queries/GetTodos/TodosVm.cs
new file mode 100644
index 0000000..bd791a5
--- /dev/null
+++ b/src/Application/TodoLists/Queries/GetTodos/TodosVm.cs
@@ -0,0 +1,9 @@
+using FastCleanArchitecture.Application.Common.Models;
+
+namespace FastCleanArchitecture.Application.TodoLists.Queries.GetTodos;
+
+public sealed class TodosVm
+{
+ public IReadOnlyCollection PriorityLevels { get; set; } = [];
+ public IReadOnlyCollection Lists { get; set; } = [];
+}
diff --git a/src/Domain/Common/BaseAuditableEntity.cs b/src/Domain/Common/BaseAuditableEntity.cs
index ab1d102..1c5ddb1 100644
--- a/src/Domain/Common/BaseAuditableEntity.cs
+++ b/src/Domain/Common/BaseAuditableEntity.cs
@@ -1,9 +1,17 @@
namespace FastCleanArchitecture.Domain.Common;
-public abstract class BaseAuditableEntity(Guid id) : BaseEntity(id)
+public abstract class BaseAuditableEntity : BaseEntity
{
+ protected BaseAuditableEntity(Guid id) : base(id)
+ {
+ }
+
+ protected BaseAuditableEntity() : base()
+ {
+ }
+
public DateTimeOffset CreatedAtUtc { get; protected set; } = DateTimeOffset.UtcNow;
public string? CreatedBy { get; protected set; }
public DateTimeOffset ModifiedAtUtc { get; protected set; }
public string? ModifiedBy { get; protected set; }
-}
\ No newline at end of file
+}
diff --git a/src/Domain/TodoItems/Events/TodoItemCreatedEvent.cs b/src/Domain/TodoItems/Events/TodoItemCreatedEvent.cs
new file mode 100644
index 0000000..34ee042
--- /dev/null
+++ b/src/Domain/TodoItems/Events/TodoItemCreatedEvent.cs
@@ -0,0 +1,5 @@
+using FastCleanArchitecture.Domain.Common;
+
+namespace FastCleanArchitecture.Domain.TodoItems.Events;
+
+public sealed record TodoItemCreatedEvent(TodoItem Item) : IDomainEvent;
diff --git a/src/Domain/TodoItems/ITodoItemRepository.cs b/src/Domain/TodoItems/ITodoItemRepository.cs
index 6b567dd..f9c87b1 100644
--- a/src/Domain/TodoItems/ITodoItemRepository.cs
+++ b/src/Domain/TodoItems/ITodoItemRepository.cs
@@ -2,5 +2,29 @@
public interface ITodoItemRepository
{
- void Add(TodoItem item);
-}
\ No newline at end of file
+ ///
+ /// Get Item by id.
+ ///
+ ///
+ ///
+ ///
+ Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
+
+ ///
+ /// Add item to the list.
+ ///
+ ///
+ void Add(TodoItem todoItem);
+
+ ///
+ /// Delete a item.
+ ///
+ ///
+ void Remove(TodoItem todoItem);
+
+ ///
+ /// Update a item.
+ ///
+ ///
+ void Update(TodoItem todoItem);
+}
diff --git a/src/Domain/TodoItems/TodoItem.cs b/src/Domain/TodoItems/TodoItem.cs
index 035ef65..20ae6a6 100644
--- a/src/Domain/TodoItems/TodoItem.cs
+++ b/src/Domain/TodoItems/TodoItem.cs
@@ -1,5 +1,6 @@
using FastCleanArchitecture.Domain.Common;
using FastCleanArchitecture.Domain.TodoItems.Enums;
+using FastCleanArchitecture.Domain.TodoItems.Events;
namespace FastCleanArchitecture.Domain.TodoItems;
@@ -15,6 +16,9 @@ private TodoItem(Guid id, Guid listId, string? title, string? note, PriorityLeve
CreatedBy = createdBy;
}
+ private TodoItem()
+ { }
+
public Guid ListId { get; private set; }
public string? Title { get; private set; }
@@ -25,8 +29,29 @@ private TodoItem(Guid id, Guid listId, string? title, string? note, PriorityLeve
public DateTime? Reminder { get; private set; }
+ public bool Done { get; private set; }
+
public static TodoItem Create(Guid listId, string? title, PriorityLevel priority = PriorityLevel.None, string? note = null, DateTime? reminder = null, string? createdBy = null)
{
- return new TodoItem(Guid.NewGuid(), listId, title, note, priority, reminder, createdBy);
+ var item = new TodoItem(Guid.NewGuid(), listId, title, note, priority, reminder, createdBy);
+
+ item.AddDomainEvent(new TodoItemCreatedEvent(item));
+
+ return item;
+ }
+
+ public static TodoItem Update(string? title, bool Done, TodoItem todoItem)
+ {
+ todoItem.Title = title;
+ todoItem.Done = Done;
+ return todoItem;
+ }
+
+ public static TodoItem UpdateDetail(Guid listId, PriorityLevel priority, string? note, TodoItem todoItem)
+ {
+ todoItem.ListId = listId;
+ todoItem.Priority = priority;
+ todoItem.Note = note;
+ return todoItem;
}
-}
\ No newline at end of file
+}
diff --git a/src/Domain/TodoLists/ITodoListRepository.cs b/src/Domain/TodoLists/ITodoListRepository.cs
index 5c1c588..2a2ef0c 100644
--- a/src/Domain/TodoLists/ITodoListRepository.cs
+++ b/src/Domain/TodoLists/ITodoListRepository.cs
@@ -2,6 +2,14 @@
public interface ITodoListRepository
{
+ ///
+ /// Get a todo list by Id.
+ ///
+ ///
+ ///
+ ///
+ Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
+
///
/// Get a todo list by title.
///
@@ -10,6 +18,13 @@ public interface ITodoListRepository
///
Task GetByTitleAsync(string title, CancellationToken cancellationToken = default);
+ ///
+ /// Get all todo lists.
+ ///
+ ///
+ ///
+ Task> GetAllAsync(CancellationToken cancellationToken = default);
+
///
/// Add a todo list.
///
diff --git a/src/Domain/TodoLists/TodoList.cs b/src/Domain/TodoLists/TodoList.cs
index 2819133..2b91fbc 100644
--- a/src/Domain/TodoLists/TodoList.cs
+++ b/src/Domain/TodoLists/TodoList.cs
@@ -6,22 +6,32 @@ namespace FastCleanArchitecture.Domain.TodoLists;
public sealed class TodoList : BaseAuditableEntity
{
- private TodoList(Guid id, string? title, Colour? colour, IList? items) : base(id)
+ private TodoList(Guid id, string? title, Colour? colour, IList items) : base(id)
{
Title = title;
Colour = colour ?? Colour.Grey;
- Items = items ?? [];
+ Items = items;
}
+ private TodoList()
+ { }
+
public string? Title { get; private set; }
- public Colour Colour { get; private set; }
- public IList Items { get; private set; }
+ public Colour Colour { get; private set; } = Colour.Grey;
+ public IList Items { get; private set; } = [];
public static TodoList Create(
string? title,
Colour? colour = null,
- IList? todoItems = null)
+ List? todoItems = null)
+ {
+ return new TodoList(Guid.NewGuid(), title, colour, todoItems ?? new List());
+ }
+
+ public static TodoList UpdateTitle(string title, TodoList todoList)
{
- return new TodoList(Guid.NewGuid(), title, colour, todoItems);
+ todoList.Title = title;
+ todoList.ModifiedAtUtc = DateTime.UtcNow;
+ return todoList;
}
}
diff --git a/src/Infrastructure/Data/ApplicationDbContextInitialiser.cs b/src/Infrastructure/Data/ApplicationDbContextInitialiser.cs
index 302db69..79f7189 100644
--- a/src/Infrastructure/Data/ApplicationDbContextInitialiser.cs
+++ b/src/Infrastructure/Data/ApplicationDbContextInitialiser.cs
@@ -78,4 +78,4 @@ public async Task TrySeedAsync()
await _context.SaveChangesAsync();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Infrastructure/Data/Configurations/TodoListConfiguration.cs b/src/Infrastructure/Data/Configurations/TodoListConfiguration.cs
index 7290e7c..bc09dd5 100644
--- a/src/Infrastructure/Data/Configurations/TodoListConfiguration.cs
+++ b/src/Infrastructure/Data/Configurations/TodoListConfiguration.cs
@@ -15,6 +15,11 @@ public void Configure(EntityTypeBuilder builder)
.HasMaxLength(200)
.IsRequired();
+ builder.HasMany(t => t.Items)
+ .WithOne()
+ .HasForeignKey(x => x.ListId)
+ .OnDelete(DeleteBehavior.Cascade);
+
builder.OwnsOne(b => b.Colour);
}
-}
\ No newline at end of file
+}
diff --git a/src/Infrastructure/Data/Migrations/20240910005539_Migrations.Designer.cs b/src/Infrastructure/Data/Migrations/20240912040604_Migrations.Designer.cs
similarity index 92%
rename from src/Infrastructure/Data/Migrations/20240910005539_Migrations.Designer.cs
rename to src/Infrastructure/Data/Migrations/20240912040604_Migrations.Designer.cs
index 8ad7b74..be490de 100644
--- a/src/Infrastructure/Data/Migrations/20240910005539_Migrations.Designer.cs
+++ b/src/Infrastructure/Data/Migrations/20240912040604_Migrations.Designer.cs
@@ -9,10 +9,10 @@
#nullable disable
-namespace FastCleanArchitecture.Infrastructure.Migrations
+namespace FastCleanArchitecture.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
- [Migration("20240910005539_Migrations")]
+ [Migration("20240912040604_Migrations")]
partial class Migrations
{
///
@@ -37,6 +37,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("CreatedBy")
.HasColumnType("nvarchar(max)");
+ b.Property("Done")
+ .HasColumnType("bit");
+
b.Property("ListId")
.HasColumnType("uniqueidentifier");
@@ -60,12 +63,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
- b.Property("TodoListId")
- .HasColumnType("uniqueidentifier");
-
b.HasKey("Id");
- b.HasIndex("TodoListId");
+ b.HasIndex("ListId");
b.ToTable("TodoItems", (string)null);
});
@@ -102,7 +102,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
b.HasOne("FastCleanArchitecture.Domain.TodoLists.TodoList", null)
.WithMany("Items")
- .HasForeignKey("TodoListId");
+ .HasForeignKey("ListId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
});
modelBuilder.Entity("FastCleanArchitecture.Domain.TodoLists.TodoList", b =>
diff --git a/src/Infrastructure/Data/Migrations/20240910005539_Migrations.cs b/src/Infrastructure/Data/Migrations/20240912040604_Migrations.cs
similarity index 87%
rename from src/Infrastructure/Data/Migrations/20240910005539_Migrations.cs
rename to src/Infrastructure/Data/Migrations/20240912040604_Migrations.cs
index 81af085..3f10766 100644
--- a/src/Infrastructure/Data/Migrations/20240910005539_Migrations.cs
+++ b/src/Infrastructure/Data/Migrations/20240912040604_Migrations.cs
@@ -3,7 +3,7 @@
#nullable disable
-namespace FastCleanArchitecture.Infrastructure.Migrations
+namespace FastCleanArchitecture.Infrastructure.Data.Migrations
{
///
public partial class Migrations : Migration
@@ -38,7 +38,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
Note = table.Column(type: "nvarchar(max)", nullable: true),
Priority = table.Column(type: "int", nullable: false),
Reminder = table.Column(type: "datetime2", nullable: true),
- TodoListId = table.Column(type: "uniqueidentifier", nullable: true),
+ Done = table.Column(type: "bit", nullable: false),
CreatedAtUtc = table.Column(type: "datetimeoffset", nullable: false),
CreatedBy = table.Column(type: "nvarchar(max)", nullable: true),
ModifiedAtUtc = table.Column(type: "datetimeoffset", nullable: false),
@@ -48,16 +48,17 @@ protected override void Up(MigrationBuilder migrationBuilder)
{
table.PrimaryKey("PK_TodoItems", x => x.Id);
table.ForeignKey(
- name: "FK_TodoItems_TodoLists_TodoListId",
- column: x => x.TodoListId,
+ name: "FK_TodoItems_TodoLists_ListId",
+ column: x => x.ListId,
principalTable: "TodoLists",
- principalColumn: "Id");
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
- name: "IX_TodoItems_TodoListId",
+ name: "IX_TodoItems_ListId",
table: "TodoItems",
- column: "TodoListId");
+ column: "ListId");
}
///
diff --git a/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs
index 5a663fd..6d60fd3 100644
--- a/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/src/Infrastructure/Data/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -3,12 +3,12 @@
using FastCleanArchitecture.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Oracle.EntityFrameworkCore.Metadata;
#nullable disable
-namespace FastCleanArchitecture.Infrastructure.Migrations
+namespace FastCleanArchitecture.Infrastructure.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
@@ -20,49 +20,49 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
- OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("FastCleanArchitecture.Domain.TodoItems.TodoItem", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("RAW(16)");
+ .HasColumnType("uniqueidentifier");
b.Property("CreatedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
+ .HasColumnType("datetimeoffset");
b.Property("CreatedBy")
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Done")
+ .HasColumnType("bit");
b.Property("ListId")
- .HasColumnType("RAW(16)");
+ .HasColumnType("uniqueidentifier");
b.Property("ModifiedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
+ .HasColumnType("datetimeoffset");
b.Property("ModifiedBy")
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
b.Property("Note")
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
b.Property("Priority")
- .HasColumnType("NUMBER(10)");
+ .HasColumnType("int");
b.Property("Reminder")
- .HasColumnType("TIMESTAMP(7)");
+ .HasColumnType("datetime2");
b.Property("Title")
.IsRequired()
.HasMaxLength(200)
- .HasColumnType("NVARCHAR2(200)");
-
- b.Property("TodoListId")
- .HasColumnType("RAW(16)");
+ .HasColumnType("nvarchar(200)");
b.HasKey("Id");
- b.HasIndex("TodoListId");
+ b.HasIndex("ListId");
b.ToTable("TodoItems", (string)null);
});
@@ -71,24 +71,24 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
b.Property("Id")
.ValueGeneratedOnAdd()
- .HasColumnType("RAW(16)");
+ .HasColumnType("uniqueidentifier");
b.Property("CreatedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
+ .HasColumnType("datetimeoffset");
b.Property("CreatedBy")
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
b.Property("ModifiedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
+ .HasColumnType("datetimeoffset");
b.Property("ModifiedBy")
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
b.Property("Title")
.IsRequired()
.HasMaxLength(200)
- .HasColumnType("NVARCHAR2(200)");
+ .HasColumnType("nvarchar(200)");
b.HasKey("Id");
@@ -99,7 +99,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
b.HasOne("FastCleanArchitecture.Domain.TodoLists.TodoList", null)
.WithMany("Items")
- .HasForeignKey("TodoListId");
+ .HasForeignKey("ListId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
});
modelBuilder.Entity("FastCleanArchitecture.Domain.TodoLists.TodoList", b =>
@@ -107,11 +109,11 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.OwnsOne("FastCleanArchitecture.Domain.TodoLists.ValueObjects.Colour", "Colour", b1 =>
{
b1.Property("TodoListId")
- .HasColumnType("RAW(16)");
+ .HasColumnType("uniqueidentifier");
b1.Property("Code")
.IsRequired()
- .HasColumnType("NVARCHAR2(2000)");
+ .HasColumnType("nvarchar(max)");
b1.HasKey("TodoListId");
diff --git a/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.Designer.cs b/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.Designer.cs
deleted file mode 100644
index 662fd9e..0000000
--- a/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.Designer.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-//
-using System;
-using FastCleanArchitecture.Infrastructure.Data;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Oracle.EntityFrameworkCore.Metadata;
-
-#nullable disable
-
-namespace FastCleanArchitecture.Infrastructure.Data.OracleMigrations
-{
- [DbContext(typeof(ApplicationDbContext))]
- [Migration("20240910180110_InitialMigrations")]
- partial class InitialMigrations
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "8.0.8")
- .HasAnnotation("Relational:MaxIdentifierLength", 128);
-
- OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
-
- modelBuilder.Entity("FastCleanArchitecture.Domain.TodoItems.TodoItem", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("RAW(16)");
-
- b.Property("CreatedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
-
- b.Property("CreatedBy")
- .HasColumnType("NVARCHAR2(2000)");
-
- b.Property("ListId")
- .HasColumnType("RAW(16)");
-
- b.Property("ModifiedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
-
- b.Property("ModifiedBy")
- .HasColumnType("NVARCHAR2(2000)");
-
- b.Property("Note")
- .HasColumnType("NVARCHAR2(2000)");
-
- b.Property("Priority")
- .HasColumnType("NUMBER(10)");
-
- b.Property("Reminder")
- .HasColumnType("TIMESTAMP(7)");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(200)
- .HasColumnType("NVARCHAR2(200)");
-
- b.Property("TodoListId")
- .HasColumnType("RAW(16)");
-
- b.HasKey("Id");
-
- b.HasIndex("TodoListId");
-
- b.ToTable("TodoItems", (string)null);
- });
-
- modelBuilder.Entity("FastCleanArchitecture.Domain.TodoLists.TodoList", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("RAW(16)");
-
- b.Property("CreatedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
-
- b.Property("CreatedBy")
- .HasColumnType("NVARCHAR2(2000)");
-
- b.Property("ModifiedAtUtc")
- .HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
-
- b.Property("ModifiedBy")
- .HasColumnType("NVARCHAR2(2000)");
-
- b.Property("Title")
- .IsRequired()
- .HasMaxLength(200)
- .HasColumnType("NVARCHAR2(200)");
-
- b.HasKey("Id");
-
- b.ToTable("TodoLists", (string)null);
- });
-
- modelBuilder.Entity("FastCleanArchitecture.Domain.TodoItems.TodoItem", b =>
- {
- b.HasOne("FastCleanArchitecture.Domain.TodoLists.TodoList", null)
- .WithMany("Items")
- .HasForeignKey("TodoListId");
- });
-
- modelBuilder.Entity("FastCleanArchitecture.Domain.TodoLists.TodoList", b =>
- {
- b.OwnsOne("FastCleanArchitecture.Domain.TodoLists.ValueObjects.Colour", "Colour", b1 =>
- {
- b1.Property("TodoListId")
- .HasColumnType("RAW(16)");
-
- b1.Property("Code")
- .IsRequired()
- .HasColumnType("NVARCHAR2(2000)");
-
- b1.HasKey("TodoListId");
-
- b1.ToTable("TodoLists");
-
- b1.WithOwner()
- .HasForeignKey("TodoListId");
- });
-
- b.Navigation("Colour")
- .IsRequired();
- });
-
- modelBuilder.Entity("FastCleanArchitecture.Domain.TodoLists.TodoList", b =>
- {
- b.Navigation("Items");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.cs b/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.cs
deleted file mode 100644
index 2a3f54f..0000000
--- a/src/Infrastructure/Data/OracleMigrations/20240910180110_InitialMigrations.cs
+++ /dev/null
@@ -1,329 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace FastCleanArchitecture.Infrastructure.Data.OracleMigrations
-{
- ///
- public partial class InitialMigrations : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterColumn(
- name: "Title",
- table: "TodoLists",
- type: "NVARCHAR2(200)",
- maxLength: 200,
- nullable: false,
- oldClrType: typeof(string),
- oldType: "nvarchar(200)",
- oldMaxLength: 200);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedBy",
- table: "TodoLists",
- type: "NVARCHAR2(2000)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedAtUtc",
- table: "TodoLists",
- type: "TIMESTAMP(7) WITH TIME ZONE",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "datetimeoffset");
-
- migrationBuilder.AlterColumn(
- name: "CreatedBy",
- table: "TodoLists",
- type: "NVARCHAR2(2000)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "CreatedAtUtc",
- table: "TodoLists",
- type: "TIMESTAMP(7) WITH TIME ZONE",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "datetimeoffset");
-
- migrationBuilder.AlterColumn(
- name: "Colour_Code",
- table: "TodoLists",
- type: "NVARCHAR2(2000)",
- nullable: false,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)");
-
- migrationBuilder.AlterColumn(
- name: "Id",
- table: "TodoLists",
- type: "RAW(16)",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "uniqueidentifier");
-
- migrationBuilder.AlterColumn(
- name: "TodoListId",
- table: "TodoItems",
- type: "RAW(16)",
- nullable: true,
- oldClrType: typeof(Guid),
- oldType: "uniqueidentifier",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "Title",
- table: "TodoItems",
- type: "NVARCHAR2(200)",
- maxLength: 200,
- nullable: false,
- oldClrType: typeof(string),
- oldType: "nvarchar(200)",
- oldMaxLength: 200);
-
- migrationBuilder.AlterColumn(
- name: "Reminder",
- table: "TodoItems",
- type: "TIMESTAMP(7)",
- nullable: true,
- oldClrType: typeof(DateTime),
- oldType: "datetime2",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "Priority",
- table: "TodoItems",
- type: "NUMBER(10)",
- nullable: false,
- oldClrType: typeof(int),
- oldType: "int");
-
- migrationBuilder.AlterColumn(
- name: "Note",
- table: "TodoItems",
- type: "NVARCHAR2(2000)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedBy",
- table: "TodoItems",
- type: "NVARCHAR2(2000)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedAtUtc",
- table: "TodoItems",
- type: "TIMESTAMP(7) WITH TIME ZONE",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "datetimeoffset");
-
- migrationBuilder.AlterColumn(
- name: "ListId",
- table: "TodoItems",
- type: "RAW(16)",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "uniqueidentifier");
-
- migrationBuilder.AlterColumn(
- name: "CreatedBy",
- table: "TodoItems",
- type: "NVARCHAR2(2000)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "nvarchar(max)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "CreatedAtUtc",
- table: "TodoItems",
- type: "TIMESTAMP(7) WITH TIME ZONE",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "datetimeoffset");
-
- migrationBuilder.AlterColumn(
- name: "Id",
- table: "TodoItems",
- type: "RAW(16)",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "uniqueidentifier");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterColumn(
- name: "Title",
- table: "TodoLists",
- type: "nvarchar(200)",
- maxLength: 200,
- nullable: false,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(200)",
- oldMaxLength: 200);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedBy",
- table: "TodoLists",
- type: "nvarchar(max)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedAtUtc",
- table: "TodoLists",
- type: "datetimeoffset",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "TIMESTAMP(7) WITH TIME ZONE");
-
- migrationBuilder.AlterColumn(
- name: "CreatedBy",
- table: "TodoLists",
- type: "nvarchar(max)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "CreatedAtUtc",
- table: "TodoLists",
- type: "datetimeoffset",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "TIMESTAMP(7) WITH TIME ZONE");
-
- migrationBuilder.AlterColumn(
- name: "Colour_Code",
- table: "TodoLists",
- type: "nvarchar(max)",
- nullable: false,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)");
-
- migrationBuilder.AlterColumn(
- name: "Id",
- table: "TodoLists",
- type: "uniqueidentifier",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "RAW(16)");
-
- migrationBuilder.AlterColumn(
- name: "TodoListId",
- table: "TodoItems",
- type: "uniqueidentifier",
- nullable: true,
- oldClrType: typeof(Guid),
- oldType: "RAW(16)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "Title",
- table: "TodoItems",
- type: "nvarchar(200)",
- maxLength: 200,
- nullable: false,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(200)",
- oldMaxLength: 200);
-
- migrationBuilder.AlterColumn(
- name: "Reminder",
- table: "TodoItems",
- type: "datetime2",
- nullable: true,
- oldClrType: typeof(DateTime),
- oldType: "TIMESTAMP(7)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "Priority",
- table: "TodoItems",
- type: "int",
- nullable: false,
- oldClrType: typeof(int),
- oldType: "NUMBER(10)");
-
- migrationBuilder.AlterColumn(
- name: "Note",
- table: "TodoItems",
- type: "nvarchar(max)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedBy",
- table: "TodoItems",
- type: "nvarchar(max)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "ModifiedAtUtc",
- table: "TodoItems",
- type: "datetimeoffset",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "TIMESTAMP(7) WITH TIME ZONE");
-
- migrationBuilder.AlterColumn(
- name: "ListId",
- table: "TodoItems",
- type: "uniqueidentifier",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "RAW(16)");
-
- migrationBuilder.AlterColumn(
- name: "CreatedBy",
- table: "TodoItems",
- type: "nvarchar(max)",
- nullable: true,
- oldClrType: typeof(string),
- oldType: "NVARCHAR2(2000)",
- oldNullable: true);
-
- migrationBuilder.AlterColumn(
- name: "CreatedAtUtc",
- table: "TodoItems",
- type: "datetimeoffset",
- nullable: false,
- oldClrType: typeof(DateTimeOffset),
- oldType: "TIMESTAMP(7) WITH TIME ZONE");
-
- migrationBuilder.AlterColumn(
- name: "Id",
- table: "TodoItems",
- type: "uniqueidentifier",
- nullable: false,
- oldClrType: typeof(Guid),
- oldType: "RAW(16)");
- }
- }
-}
diff --git a/src/Infrastructure/Data/Repositories/TodoItemRepository.cs b/src/Infrastructure/Data/Repositories/TodoItemRepository.cs
new file mode 100644
index 0000000..7744d3d
--- /dev/null
+++ b/src/Infrastructure/Data/Repositories/TodoItemRepository.cs
@@ -0,0 +1,10 @@
+using FastCleanArchitecture.Domain.TodoItems;
+
+namespace FastCleanArchitecture.Infrastructure.Data.Repositories;
+
+internal sealed class TodoItemRepository : BaseRepository, ITodoItemRepository
+{
+ public TodoItemRepository(ApplicationDbContext context) : base(context)
+ {
+ }
+}
diff --git a/src/Infrastructure/Data/Repositories/TodoListRepository.cs b/src/Infrastructure/Data/Repositories/TodoListRepository.cs
index bcfdd8c..dc91bd1 100644
--- a/src/Infrastructure/Data/Repositories/TodoListRepository.cs
+++ b/src/Infrastructure/Data/Repositories/TodoListRepository.cs
@@ -1,4 +1,5 @@
-using FastCleanArchitecture.Domain.TodoLists;
+using FastCleanArchitecture.Domain.TodoItems;
+using FastCleanArchitecture.Domain.TodoLists;
using Microsoft.EntityFrameworkCore;
namespace FastCleanArchitecture.Infrastructure.Data.Repositories;
@@ -9,8 +10,14 @@ public TodoListRepository(ApplicationDbContext context) : base(context)
{
}
+ public async Task> GetAllAsync(CancellationToken cancellationToken = default)
+ => await Context.Set()
+ .Include(t => t.Items)
+ .OrderBy(x => x.Title)
+ .ToListAsync(cancellationToken);
+
public async Task GetByTitleAsync(string title, CancellationToken cancellationToken = default)
{
- return await Context.Set().FirstOrDefaultAsync(x => x.Title!.Equals(title, StringComparison.CurrentCultureIgnoreCase));
+ return await Context.Set().FirstOrDefaultAsync(x => x.Title! == title);
}
-}
\ No newline at end of file
+}
diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Infrastructure/DependencyInjection.cs
index ae0b940..c2feffe 100644
--- a/src/Infrastructure/DependencyInjection.cs
+++ b/src/Infrastructure/DependencyInjection.cs
@@ -1,4 +1,5 @@
using FastCleanArchitecture.Domain.Common;
+using FastCleanArchitecture.Domain.TodoItems;
using FastCleanArchitecture.Domain.TodoLists;
using FastCleanArchitecture.Infrastructure.Data;
using FastCleanArchitecture.Infrastructure.Data.Repositories;
@@ -30,6 +31,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped(sp => sp.GetRequiredService());
return services;
diff --git a/templates/fca-use-case/FeatureName/Commands/FastCleanArchitectureUseCase/FastCleanArchitectureUseCase.cs b/templates/fca-use-case/FeatureName/Commands/FastCleanArchitectureUseCase/FastCleanArchitectureUseCase.cs
index a2a00a9..c5166b0 100644
--- a/templates/fca-use-case/FeatureName/Commands/FastCleanArchitectureUseCase/FastCleanArchitectureUseCase.cs
+++ b/templates/fca-use-case/FeatureName/Commands/FastCleanArchitectureUseCase/FastCleanArchitectureUseCase.cs
@@ -2,14 +2,14 @@
namespace FastCleanArchitecture.Application.FeatureName.Commands.FastCleanArchitectureUseCase;
-public record FastCleanArchitectureUseCaseCommand : ICommand