From 0978fc9a62eb737e89eed1c2b46f66eee38fc297 Mon Sep 17 00:00:00 2001 From: Alejandro Sarmiento Date: Tue, 20 Feb 2024 11:19:53 +0100 Subject: [PATCH 1/6] Unit Of Work implementado --- .../Contracts/Persistence/IAsyncRepository.cs | 4 ++ .../Contracts/Persistence/IUnitOfWork.cs | 11 +++++ .../Persistence/UnitOfWork.cs | 47 +++++++++++++++++++ .../Repositories/RepositoryBase.cs | 18 ++++++- CleanArchitecture/EliminarBinObj.ps1 | 27 +++++++++++ 5 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs create mode 100644 CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs create mode 100644 CleanArchitecture/EliminarBinObj.ps1 diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs index 0dc3232..2582155 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs @@ -21,5 +21,9 @@ namespace CleanArchitecture.Application.Contracts.Persistence Task UpdateAsync(T entity); Task DeleteAsync(T entity); + void AddEntity(T entity); + void UpdateEntity(T entity); + void DeleteEntity(T entity); + } } diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs new file mode 100644 index 0000000..6cc9154 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs @@ -0,0 +1,11 @@ +using CleanArchitecture.Domain.Common; + +namespace CleanArchitecture.Application.Contracts.Persistence +{ + public interface IUnitOfWork: IDisposable + { + IAsyncRepository Repository() where T : BaseDomainModel; + + Task Complete(); + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs new file mode 100644 index 0000000..75a0524 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs @@ -0,0 +1,47 @@ +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Domain.Common; +using System.Collections; + +namespace CleanArchitecture.Infrastructure.Persistence +{ + public class UnitOfWork : IUnitOfWork + { + private Hashtable repositories; + private readonly StreamerDbContext context; + + public UnitOfWork(StreamerDbContext _context) + { + context = _context; + } + + + + public async Task Complete() + { + return await context.SaveChangesAsync(); + } + + public void Dispose() + { + context.Dispose(); + } + + public IAsyncRepository Repository() where T : BaseDomainModel + { + if(repositories == null) + { + repositories = new Hashtable(); + } + var type = typeof(T).Name; + if(!repositories.ContainsKey(type)) + { + var repositoryType = typeof(IAsyncRepository<>); + var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); + repositories.Add(type, repositoryInstance); + } + + return (IAsyncRepository)repositories[type]; + + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs b/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs index b0eefe1..a3a4995 100644 --- a/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs +++ b/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs @@ -62,13 +62,14 @@ namespace CleanArchitecture.Infrastructure.Repositories public async Task AddAsync(T entity) { - context.Set().Add(entity); + context.Set().Add(entity); await context.SaveChangesAsync(); return entity; } public async Task UpdateAsync(T entity) { + context.Set().Attach(entity); context.Entry(entity).State = EntityState.Modified; await context.SaveChangesAsync(); return entity; @@ -80,5 +81,20 @@ namespace CleanArchitecture.Infrastructure.Repositories await context.SaveChangesAsync(); } + public void AddEntity(T entity) + { + context.Set().Add(entity); + } + + public void UpdateEntity(T entity) + { + context.Set().Attach(entity); + context.Entry(entity).State = EntityState.Modified; + } + + public void DeleteEntity(T entity) + { + context.Set().Remove(entity); + } } } diff --git a/CleanArchitecture/EliminarBinObj.ps1 b/CleanArchitecture/EliminarBinObj.ps1 new file mode 100644 index 0000000..0fc2f4d --- /dev/null +++ b/CleanArchitecture/EliminarBinObj.ps1 @@ -0,0 +1,27 @@ +# Este script de PowerShell busca y elimina los directorios 'bin' y 'obj' recursivamente desde la ubicación actual. + +# Cambia el directorio de trabajo al directorio donde se ejecuta el script +Set-Location -Path $PSScriptRoot + +# Función para buscar y eliminar directorios +function Remove-SpecifiedDirectories { + param ( + [string]$startingDirectory, + [string[]]$directoryNamesToRemove + ) + + # Buscar todos los directorios que coincidan con los nombres especificados + $directories = Get-ChildItem -Path $startingDirectory -Recurse -Directory | Where-Object { $_.Name -in $directoryNamesToRemove } + foreach ($dir in $directories) { + # Eliminar el directorio y todos sus contenidos + Remove-Item -Path $dir.FullName -Recurse -Force -ErrorAction SilentlyContinue + if (!$?) { + Write-Warning "No se pudo eliminar: $($dir.FullName)" + } else { + Write-Output "Eliminado: $($dir.FullName)" + } + } +} + +# Llama a la función con el directorio actual y los nombres de los directorios a eliminar +Remove-SpecifiedDirectories -startingDirectory (Get-Location) -directoryNamesToRemove @('bin', 'obj') From 7d3da5050e9b37ebdca28e1d0524fe28f2c7e924 Mon Sep 17 00:00:00 2001 From: Alejandro Sarmiento Date: Tue, 20 Feb 2024 11:48:18 +0100 Subject: [PATCH 2/6] Uso de unit of work para create director --- .../CleanArchitecture.Application.csproj | 3 +++ .../CreateDirector/CreateDirectorCommand.cs | 11 ++++++++ .../CreateDirectorCommandHandler.cs | 27 +++++++++++++++++++ .../CreateDirectorCommandValidator.cs | 21 +++++++++++++++ .../Mappings/MappingProfile.cs | 25 ++++++++++++----- 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommand.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandValidator.cs diff --git a/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj b/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj index 00f94ca..91ccfb2 100644 --- a/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj +++ b/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj @@ -11,6 +11,9 @@ + + + diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommand.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommand.cs new file mode 100644 index 0000000..b0dd592 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommand.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace CleanArchitecture.Application.Features.Directors.Commands.CreateDirector +{ + public class CreateDirectorCommand: IRequest + { + public string Nombre { get; set; } = string.Empty; + public string Apellido { get; set; } = string.Empty; + public int VideoId { get; set; } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs new file mode 100644 index 0000000..b6d3f0c --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Domain; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CleanArchitecture.Application.Features.Directors.Commands.CreateDirector +{ + public class CreateDirectorCommandHandler : IRequestHandler + { + private readonly ILogger logger; + private readonly IMapper mapper; + private readonly IUnitOfWork unitOfWork; + + public CreateDirectorCommandHandler(ILogger logger, IMapper mapper, IUnitOfWork unitOfWork) + { + this.logger = logger; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + } + + public Task Handle(CreateDirectorCommand request, CancellationToken cancellationToken) + { + var directorEntity = mapper.Map(request); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandValidator.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandValidator.cs new file mode 100644 index 0000000..68a3175 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandValidator.cs @@ -0,0 +1,21 @@ +using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer; +using FluentValidation; + +namespace CleanArchitecture.Application.Features.Directors.Commands.CreateDirector +{ + public class CreateDirectorCommandValidator : AbstractValidator + { + + public CreateDirectorCommandValidator() + { + RuleFor(p=>p.Nombre).NotEmpty().WithMessage("{Nombre} is required.") + .NotNull() + .MaximumLength(50).WithMessage("{Nombre} must not exceed 50 characters."); + + RuleFor(p => p.Apellido) + .NotEmpty().WithMessage("{Apellido} is required.") + .NotNull() + .MaximumLength(100).WithMessage("{Apellido} must not exceed 50 characters."); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs b/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs index 5092811..92119a5 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using CleanArchitecture.Application.Features.Directors.Commands.CreateDirector; using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer; using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer; using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; @@ -11,12 +12,24 @@ namespace CleanArchitecture.Application.Mappings { public MappingProfile() { - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); + #region Video + CreateMap(); + CreateMap(); + #endregion Video + + #region Streamer + CreateMap(); + CreateMap(); + + CreateMap(); + CreateMap(); + #endregion Streamer + + #region Director + CreateMap(); + CreateMap(); + + #endregion Director } } } From ac3d3d28b586b8ec186ce531821feafd60853a9f Mon Sep 17 00:00:00 2001 From: Alejandro Sarmiento Date: Tue, 20 Feb 2024 12:24:02 +0100 Subject: [PATCH 3/6] Crear Director --- .../Controllers/DirectorController.cs | 28 +++++++++++++++++++ .../CreateDirectorCommandHandler.cs | 14 ++++++++++ .../InfrastructureServiceRegistration.cs | 2 ++ .../Persistence/UnitOfWork.cs | 3 +- 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 CleanArchitecture/CleanArchitecture.API/Controllers/DirectorController.cs diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/DirectorController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/DirectorController.cs new file mode 100644 index 0000000..3b05a98 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/DirectorController.cs @@ -0,0 +1,28 @@ +using CleanArchitecture.Application.Features.Directors.Commands.CreateDirector; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CleanArchitecture.API.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class DirectorController : ControllerBase + { + private IMediator mediator; + + public DirectorController(IMediator mediator) + { + this.mediator = mediator; + } + + [HttpPost(Name = "CreateDirector")] + [Authorize(Roles ="Administrator")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> Create([FromBody] CreateDirectorCommand createDirectorCommand) + { + var response = await mediator.Send(createDirectorCommand); + return Ok(new { directorId = response }); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs index b6d3f0c..5a950dc 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Directors/Commands/CreateDirector/CreateDirectorCommandHandler.cs @@ -22,6 +22,20 @@ namespace CleanArchitecture.Application.Features.Directors.Commands.CreateDirect public Task Handle(CreateDirectorCommand request, CancellationToken cancellationToken) { var directorEntity = mapper.Map(request); + unitOfWork.Repository().AddEntity(directorEntity); + var response = unitOfWork.Complete(); + + if (response.Result > 0) + { + logger.LogInformation($"Director {directorEntity.Id} is successfully created."); + return Task.FromResult(directorEntity.Id); + } + else + { + logger.LogError($"Director {directorEntity.Id} creation failed."); + throw new Exception("Director creation failed."); + } + } } } diff --git a/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs b/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs index afa138f..8ca81ae 100644 --- a/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs +++ b/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs @@ -22,6 +22,8 @@ namespace CleanArchitecture.Infrastructure .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, Microsoft.Extensions.Logging.LogLevel.Information) ); + services.AddScoped(); + services.AddScoped(typeof(IAsyncRepository<>), typeof(RepositoryBase<>)); services.AddScoped(); services.AddScoped(); diff --git a/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs index 75a0524..40233d9 100644 --- a/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs +++ b/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs @@ -1,5 +1,6 @@ using CleanArchitecture.Application.Contracts.Persistence; using CleanArchitecture.Domain.Common; +using CleanArchitecture.Infrastructure.Repositories; using System.Collections; namespace CleanArchitecture.Infrastructure.Persistence @@ -35,7 +36,7 @@ namespace CleanArchitecture.Infrastructure.Persistence var type = typeof(T).Name; if(!repositories.ContainsKey(type)) { - var repositoryType = typeof(IAsyncRepository<>); + var repositoryType = typeof(RepositoryBase<>); var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); repositories.Add(type, repositoryInstance); } From f5f0379b7a71f42608cbae7e9ca47f93a82ff60e Mon Sep 17 00:00:00 2001 From: Alejandro Sarmiento Date: Tue, 20 Feb 2024 17:05:11 +0100 Subject: [PATCH 4/6] Unit of work aplicado en todos los Querys y Commands --- .../Contracts/Persistence/IUnitOfWork.cs | 3 +++ .../CreateStreamerCommandHandler.cs | 21 +++++++++++++------ .../DeleteStreamerCommandHandler.cs | 11 +++++----- .../UpdateStreamerCommandHandler.cs | 11 +++++----- .../GetVideosListQueryHandler.cs | 6 +++--- .../UnitOfWork.cs | 19 +++++++++++------ 6 files changed, 46 insertions(+), 25 deletions(-) rename CleanArchitecture/CleanArchitecture.Data/{Persistence => Repositories}/UnitOfWork.cs (68%) diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs index 6cc9154..1c5bacb 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IUnitOfWork.cs @@ -4,6 +4,9 @@ namespace CleanArchitecture.Application.Contracts.Persistence { public interface IUnitOfWork: IDisposable { + + IStreamerRepository StreamerRepository { get; } + IVideoRepository VideoRepository { get; } IAsyncRepository Repository() where T : BaseDomainModel; Task Complete(); diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/CreateStreamer/CreateStreamerCommandHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/CreateStreamer/CreateStreamerCommandHandler.cs index 2abdbc1..b2f6c39 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/CreateStreamer/CreateStreamerCommandHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/CreateStreamer/CreateStreamerCommandHandler.cs @@ -8,11 +8,11 @@ using Microsoft.Extensions.Logging; namespace CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer { - public class CreateStreamerCommandHandler(IStreamerRepository _streamerRepository, IMapper _mapper, + public class CreateStreamerCommandHandler(IUnitOfWork _unitOfWork, IMapper _mapper, IEmailService _emailService, ILogger _logger) : IRequestHandler { - private readonly IStreamerRepository streamerRepository = _streamerRepository; + private readonly IUnitOfWork unitOfWork = _unitOfWork; private readonly IMapper mapper = _mapper; private readonly IEmailService emailService = _emailService; private readonly ILogger logger = _logger; @@ -20,10 +20,19 @@ namespace CleanArchitecture.Application.Features.Streamers.Commands.CreateStream public async Task Handle(CreateStreamerCommand request, CancellationToken cancellationToken) { var streamerEntity = mapper.Map(request); - var newStreamer = await streamerRepository.AddAsync(streamerEntity); - logger.LogInformation($"Streamer {newStreamer.Id} is successfully created."); - await SendEmail(newStreamer); - return newStreamer.Id; + unitOfWork.StreamerRepository.AddEntity(streamerEntity); + var response = await unitOfWork.Complete(); + if(response > 0) + { + logger.LogInformation($"Streamer {streamerEntity.Id} is successfully created."); + await SendEmail(streamerEntity); + return streamerEntity.Id; + } + else + { + throw new Exception("Failed to create the streamer."); + } + } private async Task SendEmail(Streamer streamer) diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/DeleteStreamer/DeleteStreamerCommandHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/DeleteStreamer/DeleteStreamerCommandHandler.cs index 40f3cb2..c4e954d 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/DeleteStreamer/DeleteStreamerCommandHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/DeleteStreamer/DeleteStreamerCommandHandler.cs @@ -9,27 +9,28 @@ namespace CleanArchitecture.Application.Features.Streamers.Commands.DeleteStream { public class DeleteStreamerCommandHandler : IRequestHandler { - private readonly IStreamerRepository streamerRepository; + private readonly IUnitOfWork unitOfWork; private readonly IMapper mapper; private readonly ILogger logger; - public DeleteStreamerCommandHandler(IStreamerRepository streamerRepository, IMapper mapper, ILogger logger) + public DeleteStreamerCommandHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger logger) { - this.streamerRepository = streamerRepository; + this.unitOfWork = unitOfWork; this.mapper = mapper; this.logger = logger; } public async Task Handle(DeleteStreamerCommand request, CancellationToken cancellationToken) { - var streamerToDelete = await streamerRepository.GetByIdAsync(request.Id); + var streamerToDelete = await unitOfWork.StreamerRepository.GetByIdAsync(request.Id); if (streamerToDelete == null) { logger.LogError($"Streamer with id {request.Id} not found."); throw new NotFoundException(nameof(Streamer), request.Id); } - await streamerRepository.DeleteAsync(streamerToDelete); + unitOfWork.StreamerRepository.DeleteEntity(streamerToDelete); + await unitOfWork.Complete(); logger.LogInformation($"Streamer {streamerToDelete.Id} is successfully deleted."); return Unit.Value; diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/UpdateStreamer/UpdateStreamerCommandHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/UpdateStreamer/UpdateStreamerCommandHandler.cs index 5254f93..ffbf23f 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/UpdateStreamer/UpdateStreamerCommandHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Streamers/Commands/UpdateStreamer/UpdateStreamerCommandHandler.cs @@ -9,21 +9,21 @@ namespace CleanArchitecture.Application.Features.Streamers.Commands.UpdateStream { public class UpdateStreamerCommandHandler : IRequestHandler { - private readonly IStreamerRepository streamerRepository; + private readonly IUnitOfWork unitOfWork; private readonly IMapper mapper; private readonly ILogger logger; - public UpdateStreamerCommandHandler(IStreamerRepository streamerRepository, + public UpdateStreamerCommandHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger logger) { - this.streamerRepository = streamerRepository; + this.unitOfWork = unitOfWork; this.mapper = mapper; this.logger = logger; } public async Task Handle(UpdateStreamerCommand request, CancellationToken cancellationToken) { - var streamerToUpdate = await streamerRepository.GetByIdAsync(request.Id); + var streamerToUpdate = await unitOfWork.StreamerRepository.GetByIdAsync(request.Id); if(streamerToUpdate == null) { logger.LogError($"Streamer with id {request.Id} not found."); @@ -32,7 +32,8 @@ namespace CleanArchitecture.Application.Features.Streamers.Commands.UpdateStream mapper.Map(request, streamerToUpdate, typeof(UpdateStreamerCommand), typeof(Streamer)); - await streamerRepository.UpdateAsync(streamerToUpdate); + unitOfWork.StreamerRepository.UpdateEntity(streamerToUpdate); + await unitOfWork.Complete(); logger.LogInformation($"Streamer {streamerToUpdate.Id} is successfully updated."); diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs index dfad332..ffcd0b0 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs @@ -4,16 +4,16 @@ using MediatR; namespace CleanArchitecture.Application.Features.Videos.Queries.GetVideosList { - public class GetVideosListQueryHandler(IVideoRepository _videoRepository, IMapper _mapper) : + public class GetVideosListQueryHandler(IUnitOfWork _unitOfWork, IMapper _mapper) : IRequestHandler> { - private readonly IVideoRepository videoRepository = _videoRepository; + private readonly IUnitOfWork unitOfWork = _unitOfWork; private readonly IMapper mapper = _mapper; public async Task> Handle(GetVideosListQuery request, CancellationToken cancellationToken) { - var videoList = await videoRepository.GetVideoByUserName(request.UserName); + var videoList = await unitOfWork.VideoRepository.GetVideoByUserName(request.UserName); return mapper.Map>(videoList); } } diff --git a/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Data/Repositories/UnitOfWork.cs similarity index 68% rename from CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs rename to CleanArchitecture/CleanArchitecture.Data/Repositories/UnitOfWork.cs index 40233d9..4525df8 100644 --- a/CleanArchitecture/CleanArchitecture.Data/Persistence/UnitOfWork.cs +++ b/CleanArchitecture/CleanArchitecture.Data/Repositories/UnitOfWork.cs @@ -1,15 +1,22 @@ using CleanArchitecture.Application.Contracts.Persistence; using CleanArchitecture.Domain.Common; -using CleanArchitecture.Infrastructure.Repositories; +using CleanArchitecture.Infrastructure.Persistence; using System.Collections; -namespace CleanArchitecture.Infrastructure.Persistence +namespace CleanArchitecture.Infrastructure.Repositories { public class UnitOfWork : IUnitOfWork { private Hashtable repositories; - private readonly StreamerDbContext context; - + private readonly StreamerDbContext context; + + private IVideoRepository videoRepository; + private IStreamerRepository streamerRepository; + + + public IVideoRepository VideoRepository => videoRepository ?? new VideoRepository(context); + public IStreamerRepository StreamerRepository => streamerRepository ?? new StreamerRepository(context); + public UnitOfWork(StreamerDbContext _context) { context = _context; @@ -29,12 +36,12 @@ namespace CleanArchitecture.Infrastructure.Persistence public IAsyncRepository Repository() where T : BaseDomainModel { - if(repositories == null) + if (repositories == null) { repositories = new Hashtable(); } var type = typeof(T).Name; - if(!repositories.ContainsKey(type)) + if (!repositories.ContainsKey(type)) { var repositoryType = typeof(RepositoryBase<>); var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context); From 81a3e91d6e397995947d9e4db4251d90dce14503 Mon Sep 17 00:00:00 2001 From: Alejandro Sarmiento Date: Wed, 28 Feb 2024 22:40:34 +0100 Subject: [PATCH 5/6] Video 76 Se hizo toda la parte del Unit Of Work y estoy a punto de terminar la de tests unitarios --- .../Controllers/StreamerController.cs | 8 +- .../Controllers/VideoController.cs | 41 +++- .../CleanArchitecture.API/appsettings.json | 2 +- ...nArchitecture.Application.UnitTests.csproj | 34 +++ .../GetVideosListQueryHandlerXUnitTests.cs | 42 ++++ .../Mocks/MockStreamerRepository.cs | 12 + .../Mocks/MockUnitOfWork.cs | 18 ++ .../Mocks/MockVideoRepository.cs | 33 +++ .../CleanArchitecture.Application.csproj | 1 - .../Contracts/Persistence/IAsyncRepository.cs | 2 +- .../CreateDirector/CreateDirectorCommand.cs | 2 +- .../CreateDirectorCommandHandler.cs | 4 +- .../CreateStreamer/CreateStreamerCommand.cs | 5 +- .../CreateStreamerCommandHandler.cs | 12 +- .../DeleteStreamer/DeleteStreamerCommand.cs | 2 +- .../UpdateStreamer/UpdateStreamerCommand.cs | 7 +- .../UpdateStreamerCommandHandler.cs | 13 +- .../CreateVideo/CreateVideoCommand.cs | 11 + .../CreateVideo/CreateVideoCommandHandler.cs | 57 +++++ .../CreateVideoCommandValidator.cs | 17 ++ .../DeleteVideo/DeleteStreamerCommand.cs | 9 + .../DeleteStreamerCommandHandler.cs | 39 +++ .../UpdateVideo/UpdateStreamerCommand.cs | 12 + .../UpdateStreamerCommandHandler.cs | 43 ++++ .../UpdateStreamerCommandValidator.cs | 21 ++ .../Videos/Queries/GetVideosList/VideosVm.cs | 2 +- .../Mappings/MappingProfile.cs | 10 +- .../CleanArchitecture.Infrastructure.csproj | 4 + ...0240215190923_Second-Migration.Designer.cs | 163 ------------- .../20240215190923_Second-Migration.cs | 148 ----------- ...15201317_BaseDomainModelUpdate.Designer.cs | 215 ---------------- .../20240215201317_BaseDomainModelUpdate.cs | 187 -------------- ...23327_cleanArchitectureFromApi.Designer.cs | 230 ------------------ ...20240218123327_cleanArchitectureFromApi.cs | 72 ------ ...40221215002_Initial-Migration.Designer.cs} | 53 +++- ...cs => 20240221215002_Initial-Migration.cs} | 27 +- .../ApplicationDbContextModelSnapshot.cs | 227 ----------------- .../StreamerDbContextModelSnapshot.cs | 103 ++++++++ .../Persistence/StreamerDbContext.cs | 85 +++++-- .../Repositories/RepositoryBase.cs | 2 +- .../Repositories/StreamerRepository.cs | 2 +- .../Repositories/UnitOfWork.cs | 92 +++---- .../Repositories/VideoRepository.cs | 2 +- .../CleanArchitecture.Domain/Actor.cs | 6 +- .../Common/BaseDomainModel.cs | 2 +- .../CleanArchitecture.Domain/Video.cs | 16 +- CleanArchitecture/CleanArchitecture.sln | 9 +- .../MandarCorreo/MandarCorreo.sln | 25 ++ .../MandarCorreo/MandarCorreo.csproj | 10 + .../MandarCorreo/MandarCorreo/Program.cs | 28 +++ .../MongoProject.API/MongoProject.API.csproj | 17 ++ .../MongoProject.API/MongoProject.API.http | 6 + .../MongoProject/MongoProject.API/Program.cs | 23 ++ .../Properties/launchSettings.json | 31 +++ .../appsettings.Development.json | 8 + .../MongoProject.API/appsettings.json | 9 + .../MongoProject.Application.csproj | 9 + .../MongoProject/MongoProject.Domain/Actor.cs | 17 ++ .../Common/BaseDomainModel.cs | 18 ++ .../MongoProject.Domain/Common/ValueObject.cs | 41 ++++ .../MongoProject.Domain/Director.cs | 13 + .../MongoProject.Domain.csproj | 9 + .../MongoProject.Domain/Streamer.cs | 16 ++ .../MongoProject/MongoProject.Domain/Video.cs | 22 ++ .../MongoProject.Domain/VideoActor.cs | 12 + .../MongoProject.Infrastructure.csproj | 9 + .../MongoProject/MongoProject.sln | 60 +++++ 67 files changed, 1112 insertions(+), 1375 deletions(-) create mode 100644 CleanArchitecture/CleanArchitecture.Application.UnitTests/CleanArchitecture.Application.UnitTests.csproj create mode 100644 CleanArchitecture/CleanArchitecture.Application.UnitTests/Features/Video/Queries/GetVideosListQueryHandlerXUnitTests.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockStreamerRepository.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockUnitOfWork.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockVideoRepository.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/CreateVideo/CreateVideoCommand.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/CreateVideo/CreateVideoCommandHandler.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/CreateVideo/CreateVideoCommandValidator.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/DeleteVideo/DeleteStreamerCommand.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/DeleteVideo/DeleteStreamerCommandHandler.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/UpdateVideo/UpdateStreamerCommand.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/UpdateVideo/UpdateStreamerCommandHandler.cs create mode 100644 CleanArchitecture/CleanArchitecture.Application/Features/Videos/Commands/UpdateVideo/UpdateStreamerCommandValidator.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240215190923_Second-Migration.Designer.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240215190923_Second-Migration.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240215201317_BaseDomainModelUpdate.Designer.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240215201317_BaseDomainModelUpdate.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.Designer.cs delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.cs rename CleanArchitecture/CleanArchitecture.Data/Migrations/{20240215100525_InitialMigration.Designer.cs => 20240221215002_Initial-Migration.Designer.cs} (51%) rename CleanArchitecture/CleanArchitecture.Data/Migrations/{20240215100525_InitialMigration.cs => 20240221215002_Initial-Migration.cs} (59%) delete mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 CleanArchitecture/CleanArchitecture.Data/Migrations/StreamerDbContextModelSnapshot.cs create mode 100644 CleanArchitectureRedis/MandarCorreo/MandarCorreo.sln create mode 100644 CleanArchitectureRedis/MandarCorreo/MandarCorreo/MandarCorreo.csproj create mode 100644 CleanArchitectureRedis/MandarCorreo/MandarCorreo/Program.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/MongoProject.API.csproj create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/MongoProject.API.http create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/Program.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/Properties/launchSettings.json create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/appsettings.Development.json create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.API/appsettings.json create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Application/MongoProject.Application.csproj create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Actor.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Common/BaseDomainModel.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Common/ValueObject.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Director.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/MongoProject.Domain.csproj create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Streamer.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/Video.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Domain/VideoActor.cs create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.Infrastructure/MongoProject.Infrastructure.csproj create mode 100644 CleanArchitectureRedis/MongoProject/MongoProject.sln diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs index 1128e83..80547c1 100644 --- a/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs @@ -25,7 +25,7 @@ namespace CleanArchitecture.API.Controllers public async Task> CreateStreamer([FromBody] CreateStreamerCommand command) { var response = await mediator.Send(command); - return Ok(new { StreamerId = response }); + return Ok(response); } [HttpPut(Name = "UpdateStreamer")] @@ -35,8 +35,8 @@ namespace CleanArchitecture.API.Controllers [ProducesDefaultResponseType] public async Task UpdateStreamer([FromBody] UpdateStreamerCommand command) { - await mediator.Send(command); - return NoContent(); + var createdStreamer = await mediator.Send(command); + return Ok(createdStreamer); } [HttpDelete("{id}", Name = "DeleteStreamer")] @@ -44,7 +44,7 @@ namespace CleanArchitecture.API.Controllers [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesDefaultResponseType] - public async Task DeleteStreamer(int id) + public async Task DeleteStreamer(Guid id) { var request = new DeleteStreamerCommand() { Id = id }; await mediator.Send(request); diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs index c5a464b..54caf39 100644 --- a/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs @@ -1,4 +1,11 @@ -using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; +using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer; +using CleanArchitecture.Application.Features.Streamers.Commands.DeleteStreamer; +using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer; +using CleanArchitecture.Application.Features.Videos.Commands.CreateVideo; +using CleanArchitecture.Application.Features.Videos.Commands.DeleteVideo; +using CleanArchitecture.Application.Features.Videos.Commands.UpdateVideo; +using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; +using CleanArchitecture.Domain; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -27,5 +34,37 @@ namespace CleanArchitecture.API.Controllers var videos = await mediator.Send(query); return Ok(videos); } + + [HttpPost(Name = "CreateVideo")] + [Authorize(Roles = "Administrator")] + [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] + public async Task> CreateVideo([FromBody] CreateVideoCommand command) + { + var response = await mediator.Send(command); + return Ok(response); + } + + [HttpPut(Name = "UpdateVideo")] + [Authorize(Roles = "Administrator")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [ProducesDefaultResponseType] + public async Task> UpdateVideo([FromBody] UpdateVideoCommand command) + { + var response = await mediator.Send(command); + return Ok(response); + } + + [HttpDelete("{id}", Name = "DeleteVideo")] + [Authorize(Roles = "Administrator")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [ProducesDefaultResponseType] + public async Task DeleteVideo(Guid id) + { + var request = new DeleteVideoCommand() { Id = id }; + await mediator.Send(request); + return NoContent(); + } } } diff --git a/CleanArchitecture/CleanArchitecture.API/appsettings.json b/CleanArchitecture/CleanArchitecture.API/appsettings.json index 7ec6057..351455a 100644 --- a/CleanArchitecture/CleanArchitecture.API/appsettings.json +++ b/CleanArchitecture/CleanArchitecture.API/appsettings.json @@ -1,7 +1,7 @@ { "ConnectionStrings": { - "ConnectionString": "server=localhost;database=CleanArchitecture;user=root;password=securePassword", + "ConnectionString": "server=localhost;database=CleanArchitectureV2;user=root;password=securePassword", "IdentityConnectionString": "server=localhost;database=CleanArchitecture.Security;user=root;password=securePassword" }, "EmailSettings": { diff --git a/CleanArchitecture/CleanArchitecture.Application.UnitTests/CleanArchitecture.Application.UnitTests.csproj b/CleanArchitecture/CleanArchitecture.Application.UnitTests/CleanArchitecture.Application.UnitTests.csproj new file mode 100644 index 0000000..ef074c7 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application.UnitTests/CleanArchitecture.Application.UnitTests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/CleanArchitecture/CleanArchitecture.Application.UnitTests/Features/Video/Queries/GetVideosListQueryHandlerXUnitTests.cs b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Features/Video/Queries/GetVideosListQueryHandlerXUnitTests.cs new file mode 100644 index 0000000..31e2911 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Features/Video/Queries/GetVideosListQueryHandlerXUnitTests.cs @@ -0,0 +1,42 @@ +using AutoMapper; +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; +using CleanArchitecture.Application.Mappings; +using CleanArchitecture.Application.UnitTests.Mocks; +using Moq; +using Shouldly; +using Xunit; + +namespace CleanArchitecture.Application.UnitTests.Features.Video.Queries +{ + public class GetVideosListQueryHandlerXUnitTests + { + + private readonly IMapper mapper; + private readonly Mock mockUnitOfWork; + + public GetVideosListQueryHandlerXUnitTests() + { + mockUnitOfWork = MockUnitOfWork.GetUnitOfWork(); + var mapperConfiguration = new MapperConfiguration(cfg => + { + cfg.AddProfile(); + }); + mapper = mapperConfiguration.CreateMapper(); + + } + + [Fact] + public async Task GetVideoListTest() + { + var handler = new GetVideosListQueryHandler(mockUnitOfWork.Object, mapper); + var request = new GetVideosListQuery("Alex"); + var result = await handler.Handle(request, CancellationToken.None); + + result.ShouldBeOfType>(); + result.Count.ShouldBe(1); + + + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockStreamerRepository.cs b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockStreamerRepository.cs new file mode 100644 index 0000000..0b47829 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockStreamerRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CleanArchitecture.Application.UnitTests.Mocks +{ + internal class MockStreamerRepository + { + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockUnitOfWork.cs b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockUnitOfWork.cs new file mode 100644 index 0000000..505848a --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockUnitOfWork.cs @@ -0,0 +1,18 @@ +using CleanArchitecture.Application.Contracts.Persistence; +using Moq; + +namespace CleanArchitecture.Application.UnitTests.Mocks +{ + public static class MockUnitOfWork + { + + public static Mock GetUnitOfWork() + { + var mockUnitOfWork = new Mock(); + var mockVideoRepository = MockVideoRepository.GetVideoRepository(); + mockUnitOfWork.Setup(uow => uow.VideoRepository).Returns(mockVideoRepository.Object); + return mockUnitOfWork; + } + + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockVideoRepository.cs b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockVideoRepository.cs new file mode 100644 index 0000000..1aafed0 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application.UnitTests/Mocks/MockVideoRepository.cs @@ -0,0 +1,33 @@ +using AutoFixture; +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Domain; +using CleanArchitecture.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Moq; +using CleanArchitecture.Infrastructure.Repositories; + +namespace CleanArchitecture.Application.UnitTests.Mocks +{ + public static class MockVideoRepository + { + public static Mock GetVideoRepository() + { + var fixture = new Fixture(); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + var videos = fixture.CreateMany