From d8a8296d932b8666099c33ccc87b8b965a5853dd Mon Sep 17 00:00:00 2001 From: Kasper Kristensen Date: Thu, 21 Mar 2024 12:41:56 +0100 Subject: [PATCH] Apply api high quality rules. --- .../CreatePlayer/CreatePlayerCommandHander.cs | 33 ++++++++---- .../GetPlayerByIdQueryHandler.cs | 13 ++--- .../Repositories/IPlayerRepository.cs | 10 ---- .../Players/Errors/PlayerExists.cs | 9 ++++ .../Players/Errors/PlayerNotFound.cs | 9 ++++ .../Repositories/Players/IPlayerRepository.cs | 11 ++++ .../Football.Management.Persistence.csproj | 4 ++ .../Repositories/PlayerRepositoryInMemory.cs | 40 --------------- .../Players/PlayerRepositoryInMemory.cs | 51 +++++++++++++++++++ .../Players/Controllers/PlayersController.cs | 13 +++-- .../Players/Requests/CreatePlayerRequest.cs | 3 -- .../Players/Requests/PlayerPostRequest.cs | 3 ++ Football.Management.Web/Program.cs | 4 +- 13 files changed, 124 insertions(+), 79 deletions(-) delete mode 100644 Football.Management.Domain/Repositories/IPlayerRepository.cs create mode 100644 Football.Management.Domain/Repositories/Players/Errors/PlayerExists.cs create mode 100644 Football.Management.Domain/Repositories/Players/Errors/PlayerNotFound.cs create mode 100644 Football.Management.Domain/Repositories/Players/IPlayerRepository.cs delete mode 100644 Football.Management.Persistence/Repositories/PlayerRepositoryInMemory.cs create mode 100644 Football.Management.Persistence/Repositories/Players/PlayerRepositoryInMemory.cs delete mode 100644 Football.Management.Presentation/Players/Requests/CreatePlayerRequest.cs create mode 100644 Football.Management.Presentation/Players/Requests/PlayerPostRequest.cs diff --git a/Football.Management.Application/Players/Commands/CreatePlayer/CreatePlayerCommandHander.cs b/Football.Management.Application/Players/Commands/CreatePlayer/CreatePlayerCommandHander.cs index 07627af..a667a10 100644 --- a/Football.Management.Application/Players/Commands/CreatePlayer/CreatePlayerCommandHander.cs +++ b/Football.Management.Application/Players/Commands/CreatePlayer/CreatePlayerCommandHander.cs @@ -2,7 +2,7 @@ using Football.Management.Application.Abstractions; using Football.Management.Domain.Entities; using Football.Management.Domain.Identities; -using Football.Management.Domain.Repositories; +using Football.Management.Domain.Repositories.Players; using Football.Management.Domain.ValueObjects; namespace Football.Management.Application.Players.Commands.CreatePlayer; @@ -16,41 +16,41 @@ public CreatePlayerCommandHander(IPlayerRepository playerRepository) _playerRepository = playerRepository; } - public async Task> Handle(CreatePlayerCommand request, CancellationToken cancellationToken) + public Task> Handle(CreatePlayerCommand request, CancellationToken cancellationToken) { var firstName = PlayerFirstName.Create(request.FirstName); if (firstName.IsFailed) { - return firstName.ToResult(); + return Task.FromResult(firstName.ToResult()); } var lastName = PlayerLastName.Create(request.LastName); if (lastName.IsFailed) { - return lastName.ToResult(); + return Task.FromResult(lastName.ToResult()); } var attackSkill = PlayerAttackSkill.Create(request.AttackSkill); if (attackSkill.IsFailed) { - return attackSkill.ToResult(); + return Task.FromResult(attackSkill.ToResult()); } var midfieldSkill = PlayerMidfieldSkill.Create(request.MidfieldSkill); if (midfieldSkill.IsFailed) { - return midfieldSkill.ToResult(); + return Task.FromResult(midfieldSkill.ToResult()); } var defenseSkill = PlayerDefenseSkill.Create(request.DefenseSkill); if (defenseSkill.IsFailed) { - return defenseSkill.ToResult(); + return Task.FromResult(defenseSkill.ToResult()); } var player = Player.Create( @@ -62,11 +62,24 @@ public async Task> Handle(CreatePlayerCommand request, Cancella if (player.IsFailed) { - return player.ToResult(); + return Task.FromResult(player.ToResult()); } - await _playerRepository.AddAsync(player.Value, cancellationToken); + return _playerRepository + .AddAsync(player.Value, cancellationToken) + .ContinueWith(t => + { + if (t.IsFaulted) + { + return Result.Fail("Task has failed."); + } + + if (t.Result.IsFailed) + { + return t.Result.ToResult(); + } - return player.Value.Id; + return Result.Ok(player.Value.Id); + }); } } diff --git a/Football.Management.Application/Players/Queries/GetPlayerById/GetPlayerByIdQueryHandler.cs b/Football.Management.Application/Players/Queries/GetPlayerById/GetPlayerByIdQueryHandler.cs index c9d8343..76845e6 100644 --- a/Football.Management.Application/Players/Queries/GetPlayerById/GetPlayerByIdQueryHandler.cs +++ b/Football.Management.Application/Players/Queries/GetPlayerById/GetPlayerByIdQueryHandler.cs @@ -1,7 +1,7 @@ using FluentResults; using Football.Management.Application.Abstractions; using Football.Management.Domain.Entities; -using Football.Management.Domain.Repositories; +using Football.Management.Domain.Repositories.Players; namespace Football.Management.Application.Players.Queries.GetPlayerById; @@ -14,15 +14,8 @@ public GetPlayerByIdQueryHandler(IPlayerRepository playerRepository) _playerRepository = playerRepository; } - public async Task> Handle(GetPlayerByIdQuery request, CancellationToken cancellationToken) + public Task> Handle(GetPlayerByIdQuery request, CancellationToken cancellationToken) { - var player = await _playerRepository.GetAsync(request.PlayerId, cancellationToken); - - if (player is null) - { - return Result.Fail("Player was not found."); - } - - return player; + return _playerRepository.GetAsync(request.PlayerId, cancellationToken); } } diff --git a/Football.Management.Domain/Repositories/IPlayerRepository.cs b/Football.Management.Domain/Repositories/IPlayerRepository.cs deleted file mode 100644 index d247873..0000000 --- a/Football.Management.Domain/Repositories/IPlayerRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Football.Management.Domain.Entities; -using Football.Management.Domain.Identities; - -namespace Football.Management.Domain.Repositories; - -public interface IPlayerRepository -{ - Task AddAsync(Player player, CancellationToken cancellationToken); - Task GetAsync(PlayerId playerId, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/Football.Management.Domain/Repositories/Players/Errors/PlayerExists.cs b/Football.Management.Domain/Repositories/Players/Errors/PlayerExists.cs new file mode 100644 index 0000000..b5acdb0 --- /dev/null +++ b/Football.Management.Domain/Repositories/Players/Errors/PlayerExists.cs @@ -0,0 +1,9 @@ +using FluentResults; +using Football.Management.Domain.Entities; + +namespace Football.Management.Domain.Repositories.Players.Errors; + +public class PlayerExists(Player player) : Error +{ + public Player Player { get; } = player; +} \ No newline at end of file diff --git a/Football.Management.Domain/Repositories/Players/Errors/PlayerNotFound.cs b/Football.Management.Domain/Repositories/Players/Errors/PlayerNotFound.cs new file mode 100644 index 0000000..640481e --- /dev/null +++ b/Football.Management.Domain/Repositories/Players/Errors/PlayerNotFound.cs @@ -0,0 +1,9 @@ +using FluentResults; +using Football.Management.Domain.Identities; + +namespace Football.Management.Domain.Repositories.Players.Errors; + +public class PlayerNotFound(PlayerId PlayerId) : Error +{ + public PlayerId PlayerId { get; } = PlayerId; +} \ No newline at end of file diff --git a/Football.Management.Domain/Repositories/Players/IPlayerRepository.cs b/Football.Management.Domain/Repositories/Players/IPlayerRepository.cs new file mode 100644 index 0000000..a0476be --- /dev/null +++ b/Football.Management.Domain/Repositories/Players/IPlayerRepository.cs @@ -0,0 +1,11 @@ +using FluentResults; +using Football.Management.Domain.Entities; +using Football.Management.Domain.Identities; + +namespace Football.Management.Domain.Repositories.Players; + +public interface IPlayerRepository +{ + Task AddAsync(Player player, CancellationToken cancellationToken); + Task> GetAsync(PlayerId playerId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/Football.Management.Persistence/Football.Management.Persistence.csproj b/Football.Management.Persistence/Football.Management.Persistence.csproj index be4a95d..a15410a 100644 --- a/Football.Management.Persistence/Football.Management.Persistence.csproj +++ b/Football.Management.Persistence/Football.Management.Persistence.csproj @@ -10,4 +10,8 @@ enable + + + + diff --git a/Football.Management.Persistence/Repositories/PlayerRepositoryInMemory.cs b/Football.Management.Persistence/Repositories/PlayerRepositoryInMemory.cs deleted file mode 100644 index bf23708..0000000 --- a/Football.Management.Persistence/Repositories/PlayerRepositoryInMemory.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Football.Management.Domain.Entities; -using Football.Management.Domain.Identities; -using Football.Management.Domain.Repositories; -using Football.Management.Domain.ValueObjects; - -namespace Football.Management.Persistence.Repositories; - -public sealed class PlayerRepositoryInMemory : IPlayerRepository -{ - private List _players = new(); - - public PlayerRepositoryInMemory() - { - var player = Player.Create( - PlayerFirstName.Create("Kamil").ValueOrDefault, - PlayerLastName.Create("Grabara").ValueOrDefault, - PlayerAttackSkill.Create(0).ValueOrDefault, - PlayerMidfieldSkill.Create(5).ValueOrDefault, - PlayerDefenseSkill.Create(10).ValueOrDefault); - - _players.Add(player.Value); - } - - public async Task AddAsync(Player player, CancellationToken cancellationToken) - { - var result = await GetAsync(player.Id, cancellationToken); - - if (result is not null) - { - return; - } - - _players.Add(player); - } - - public Task GetAsync(PlayerId playerId, CancellationToken cancellationToken) - { - return Task.FromResult(_players.FirstOrDefault(i => i.Id.Id == playerId.Id)); - } -} \ No newline at end of file diff --git a/Football.Management.Persistence/Repositories/Players/PlayerRepositoryInMemory.cs b/Football.Management.Persistence/Repositories/Players/PlayerRepositoryInMemory.cs new file mode 100644 index 0000000..0e73e01 --- /dev/null +++ b/Football.Management.Persistence/Repositories/Players/PlayerRepositoryInMemory.cs @@ -0,0 +1,51 @@ +using FluentResults; +using Football.Management.Domain.Entities; +using Football.Management.Domain.Identities; +using Football.Management.Domain.Repositories.Players; +using Football.Management.Domain.Repositories.Players.Errors; +using Football.Management.Domain.ValueObjects; + +namespace Football.Management.Persistence.Repositories.Players; + +public sealed class PlayerRepositoryInMemory : IPlayerRepository +{ + private List _players = new(); + + public PlayerRepositoryInMemory() + { + var player = Player.Create( + PlayerFirstName.Create("Kamil").ValueOrDefault, + PlayerLastName.Create("Grabara").ValueOrDefault, + PlayerAttackSkill.Create(0).ValueOrDefault, + PlayerMidfieldSkill.Create(5).ValueOrDefault, + PlayerDefenseSkill.Create(10).ValueOrDefault); + + _players.Add(player.Value); + } + + public async Task AddAsync(Player player, CancellationToken cancellationToken) + { + var playerResult = await GetAsync(player.Id, cancellationToken); + + if (playerResult.IsSuccess) + { + return Result.Fail(new PlayerExists(player)); + } + + _players.Add(player); + + return Result.Ok(); + } + + public Task> GetAsync(PlayerId playerId, CancellationToken cancellationToken) + { + var player = _players.FirstOrDefault(i => i.Id.Id == playerId.Id); + + if (player is null) + { + return Task.FromResult(Result.Fail(new PlayerNotFound(playerId))); + } + + return Task.FromResult(Result.Ok(player)); + } +} diff --git a/Football.Management.Presentation/Players/Controllers/PlayersController.cs b/Football.Management.Presentation/Players/Controllers/PlayersController.cs index 131d83a..bfa6fd0 100644 --- a/Football.Management.Presentation/Players/Controllers/PlayersController.cs +++ b/Football.Management.Presentation/Players/Controllers/PlayersController.cs @@ -5,11 +5,12 @@ using Football.Management.Domain.Identities; using Football.Management.Application.Players.Queries.GetPlayerById; using Football.Management.Presentation.Players.Responses; +using Football.Management.Domain.Repositories.Players.Errors; namespace Football.Management.Presentation.Players.Controllers; [ApiController] -[Route("api/players")] +[Route("api/player")] public sealed class PlayersController : ControllerBase { private readonly ISender _sender; @@ -20,11 +21,11 @@ public PlayersController(ISender sender) } [HttpPost] - public async Task CreatePlayer(CreatePlayerRequest request, CancellationToken cancellationToken) + public async Task CreatePlayer(PlayerPostRequest request, CancellationToken cancellationToken) { var command = new CreatePlayerCommand(request.FirstName, request.LastName, request.AttackSkill, request.MidfieldSkill, request.DefenseSkill); var result = await _sender.Send(command, cancellationToken); - return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Errors); + return result.IsSuccess ? Ok(result.Value) : BadRequest(new { result.Errors }); } [HttpGet("{playerId}")] @@ -44,6 +45,10 @@ public async Task GetPlayerById(Guid playerId, CancellationToken player.DefenseSkill.Value); return Ok(response); } - return NotFound(result.Errors); + if (result.Errors.Select(e => e.GetType()).Contains(typeof(PlayerNotFound))) + { + return NoContent(); + } + return Ok(result.Errors); } } diff --git a/Football.Management.Presentation/Players/Requests/CreatePlayerRequest.cs b/Football.Management.Presentation/Players/Requests/CreatePlayerRequest.cs deleted file mode 100644 index 8cafdb3..0000000 --- a/Football.Management.Presentation/Players/Requests/CreatePlayerRequest.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Football.Management.Presentation.Players.Requests; - -public sealed record CreatePlayerRequest(string FirstName, string LastName, int AttackSkill, int MidfieldSkill, int DefenseSkill); \ No newline at end of file diff --git a/Football.Management.Presentation/Players/Requests/PlayerPostRequest.cs b/Football.Management.Presentation/Players/Requests/PlayerPostRequest.cs new file mode 100644 index 0000000..6077795 --- /dev/null +++ b/Football.Management.Presentation/Players/Requests/PlayerPostRequest.cs @@ -0,0 +1,3 @@ +namespace Football.Management.Presentation.Players.Requests; + +public sealed record PlayerPostRequest(string FirstName, string LastName, int AttackSkill, int MidfieldSkill, int DefenseSkill); \ No newline at end of file diff --git a/Football.Management.Web/Program.cs b/Football.Management.Web/Program.cs index 6db8d8a..b9b7a3f 100644 --- a/Football.Management.Web/Program.cs +++ b/Football.Management.Web/Program.cs @@ -1,4 +1,4 @@ -using Football.Management.Domain.Repositories; +using Football.Management.Domain.Repositories.Players; var builder = WebApplication.CreateBuilder(args); @@ -15,7 +15,7 @@ // .AddClasses(false) // .AsImplementedInterfaces() // .WithScopedLifetime()); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddMediatR(conf => conf.RegisterServicesFromAssembly(Football.Management.Application.AssemblyReference.Assembly)); builder.Services.AddControllers().AddApplicationPart(Football.Management.Presentation.AssemblyReference.Assembly); builder.Services.AddSwaggerGen();