diff --git a/CleanArchitecture/.editorconfig b/CleanArchitecture/.editorconfig new file mode 100644 index 0000000..8922af1 --- /dev/null +++ b/CleanArchitecture/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CA1860: Evitar usar el método de extensión "Enumerable.Any()" +dotnet_diagnostic.CA1860.severity = silent diff --git a/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj new file mode 100644 index 0000000..7b2a188 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.http b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.http new file mode 100644 index 0000000..6012c14 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.http @@ -0,0 +1,6 @@ +@CleanArchitecture.API_HostAddress = http://localhost:5124 + +GET {{CleanArchitecture.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs new file mode 100644 index 0000000..f0c6cec --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs @@ -0,0 +1,51 @@ +using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer; +using CleanArchitecture.Application.Features.Streamers.Commands.DeleteStreamer; +using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace CleanArchitecture.API.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class StreamerController : ControllerBase + { + private readonly IMediator mediator; + public StreamerController(IMediator _mediator) + { + mediator = _mediator; + } + + [HttpPost(Name = "CreateStreamer")] + [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] + public async Task> CreateStreamer([FromBody] CreateStreamerCommand command) + { + var response = await mediator.Send(command); + return Ok(response); + } + + [HttpPut(Name = "UpdateStreamer")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [ProducesDefaultResponseType] + public async Task UpdateStreamer([FromBody] UpdateStreamerCommand command) + { + await mediator.Send(command); + return NoContent(); + } + + [HttpDelete("{id}", Name = "DeleteStreamer")] + [ProducesResponseType((int)HttpStatusCode.NoContent)] + [ProducesResponseType((int)HttpStatusCode.NotFound)] + [ProducesDefaultResponseType] + public async Task DeleteStreamer(int id) + { + var request = new DeleteStreamerCommand() { Id = id }; + await mediator.Send(request); + return NoContent(); + } + + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs new file mode 100644 index 0000000..3d4e48e --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs @@ -0,0 +1,29 @@ +using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace CleanArchitecture.API.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class VideoController : ControllerBase + { + private readonly IMediator mediator; + + public VideoController(IMediator _mediator) + { + mediator = _mediator; + } + + [HttpGet("{username}", Name = "GetVideo")] + [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] + public async Task>> GetVideosByUserName(string username) + { + var query = new GetVideosListQuery(username); + var videos = await mediator.Send(query); + return Ok(videos); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Program.cs b/CleanArchitecture/CleanArchitecture.API/Program.cs new file mode 100644 index 0000000..6e3fd1b --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Program.cs @@ -0,0 +1,23 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/CleanArchitecture/CleanArchitecture.API/Properties/launchSettings.json b/CleanArchitecture/CleanArchitecture.API/Properties/launchSettings.json new file mode 100644 index 0000000..88c1e6c --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:32905", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5124", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/appsettings.Development.json b/CleanArchitecture/CleanArchitecture.API/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/appsettings.json b/CleanArchitecture/CleanArchitecture.API/appsettings.json new file mode 100644 index 0000000..4d56694 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/CleanArchitecture/CleanArchitecture.Application/ApplicationServiceRegistration.cs b/CleanArchitecture/CleanArchitecture.Application/ApplicationServiceRegistration.cs new file mode 100644 index 0000000..1708f38 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/ApplicationServiceRegistration.cs @@ -0,0 +1,24 @@ +using CleanArchitecture.Application.Behaviours; +using FluentValidation; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using System.Reflection; + +namespace CleanArchitecture.Application +{ + public static class ApplicationServiceRegistration + { + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + services.AddAutoMapper(Assembly.GetExecutingAssembly()); + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>)); + + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>)); + + return services; + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Behaviours/UnhandledExceptionBehaviour.cs b/CleanArchitecture/CleanArchitecture.Application/Behaviours/UnhandledExceptionBehaviour.cs new file mode 100644 index 0000000..157fe52 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Behaviours/UnhandledExceptionBehaviour.cs @@ -0,0 +1,28 @@ +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CleanArchitecture.Application.Behaviours +{ + public class UnhandledExceptionBehaviour : IPipelineBehavior where TRequest : IRequest + { + private readonly ILogger logger; + + public UnhandledExceptionBehaviour(ILogger _logger) + { + logger = _logger; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + try + { + return await next(); + }catch (Exception ex) + { + var requestName = typeof(TRequest).Name; + logger.LogError(ex, "CleanArchitecture Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + throw; + } + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Behaviours/ValidationBehaviour.cs b/CleanArchitecture/CleanArchitecture.Application/Behaviours/ValidationBehaviour.cs new file mode 100644 index 0000000..c980240 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Behaviours/ValidationBehaviour.cs @@ -0,0 +1,32 @@ +using ValidationException = CleanArchitecture.Application.Exceptions.ValidationException; +using FluentValidation; +using MediatR; + +namespace CleanArchitecture.Application.Behaviours +{ + public class ValidationBehaviour : IPipelineBehavior where TRequest : IRequest + { + private readonly IEnumerable> validators; + + public ValidationBehaviour(IEnumerable> _validators) + { + this.validators = _validators; + } + + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (validators.Any()) + { + var context = new ValidationContext(request); + var validationResults = await Task.WhenAll(validators.Select(v => v.ValidateAsync(context, cancellationToken))); + var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList(); + if (failures.Any()) + { + throw new ValidationException(failures); + } + } + + return await next(); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj b/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj new file mode 100644 index 0000000..00f94ca --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/CleanArchitecture.Application.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Infrastructure/IEmailService.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Infrastructure/IEmailService.cs new file mode 100644 index 0000000..03639e9 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Infrastructure/IEmailService.cs @@ -0,0 +1,11 @@ +using CleanArchitecture.Application.Models; + +namespace CleanArchitecture.Application.Contracts.Infrastructure +{ + public interface IEmailService + { + + Task SendEmail(Email email); + + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs new file mode 100644 index 0000000..0dc3232 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IAsyncRepository.cs @@ -0,0 +1,25 @@ +using CleanArchitecture.Domain.Common; +using System.Linq.Expressions; + +namespace CleanArchitecture.Application.Contracts.Persistence +{ + public interface IAsyncRepository where T: BaseDomainModel + { + Task> GetAllAsync(); + Task> GetAsync(Expression>? predicate); + Task> GetAsync(Expression>? predicate = null, + Func, IOrderedQueryable>? orderBy = null, + string includeString = null, + bool disableTracking = true); + Task> GetAsync(Expression>? predicate = null, + Func, IOrderedQueryable>? orderBy = null, + List>>? includes = null, + bool disableTracking = true); + Task GetByIdAsync(int id); + + Task AddAsync(T entity); + Task UpdateAsync(T entity); + Task DeleteAsync(T entity); + + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IStreamerRepository.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IStreamerRepository.cs new file mode 100644 index 0000000..63900d9 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IStreamerRepository.cs @@ -0,0 +1,14 @@ +using CleanArchitecture.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CleanArchitecture.Application.Contracts.Persistence +{ + public interface IStreamerRepository: IAsyncRepository + { + + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IVideoRepository.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IVideoRepository.cs new file mode 100644 index 0000000..443546f --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Persistence/IVideoRepository.cs @@ -0,0 +1,10 @@ +using CleanArchitecture.Domain; + +namespace CleanArchitecture.Application.Contracts.Persistence +{ + public interface IVideoRepository : IAsyncRepository