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