diff --git a/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj index 7b2a188..c854523 100644 --- a/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj +++ b/CleanArchitecture/CleanArchitecture.API/CleanArchitecture.API.csproj @@ -7,12 +7,17 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/AccountController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/AccountController.cs new file mode 100644 index 0000000..903246f --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/AccountController.cs @@ -0,0 +1,34 @@ +using CleanArchitecture.Application.Contracts.Identity; +using CleanArchitecture.Application.Models.Identity; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity.Data; +using Microsoft.AspNetCore.Mvc; + +namespace CleanArchitecture.API.Controllers +{ + [Route("api/v1/[controller]")] + [ApiController] + public class AccountController : ControllerBase + { + private readonly IAuthService authService; + + public AccountController(IAuthService _authService) + { + authService = _authService; + } + + [HttpPost("login")] + public async Task> Login([FromBody] AuthRequest request) + { + var response = await authService.Login(request); + return Ok(response); + } + + [HttpPost("register")] + public async Task> Register([FromBody] RegistrationRequest request) + { + var response = await authService.Register(request); + return Ok(response); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs index f0c6cec..1128e83 100644 --- a/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/StreamerController.cs @@ -2,6 +2,7 @@ using CleanArchitecture.Application.Features.Streamers.Commands.DeleteStreamer; using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Net; @@ -13,20 +14,22 @@ namespace CleanArchitecture.API.Controllers public class StreamerController : ControllerBase { private readonly IMediator mediator; - public StreamerController(IMediator _mediator) - { - mediator = _mediator; + public StreamerController(IMediator _mediator) + { + mediator = _mediator; } [HttpPost(Name = "CreateStreamer")] + [Authorize(Roles = "Administrator")] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] public async Task> CreateStreamer([FromBody] CreateStreamerCommand command) { var response = await mediator.Send(command); - return Ok(response); + return Ok(new { StreamerId = response }); } [HttpPut(Name = "UpdateStreamer")] + [Authorize(Roles = "Administrator")] [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesDefaultResponseType] @@ -37,6 +40,7 @@ namespace CleanArchitecture.API.Controllers } [HttpDelete("{id}", Name = "DeleteStreamer")] + [Authorize(Roles = "Administrator")] [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesDefaultResponseType] diff --git a/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs index 3d4e48e..c5a464b 100644 --- a/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs +++ b/CleanArchitecture/CleanArchitecture.API/Controllers/VideoController.cs @@ -1,5 +1,6 @@ using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; using MediatR; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Net; @@ -18,6 +19,7 @@ namespace CleanArchitecture.API.Controllers } [HttpGet("{username}", Name = "GetVideo")] + [Authorize] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] public async Task>> GetVideosByUserName(string username) { diff --git a/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorException.cs b/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorException.cs new file mode 100644 index 0000000..e665036 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorException.cs @@ -0,0 +1,13 @@ +namespace CleanArchitecture.API.Errors +{ + public class CodeErrorException : CodeErrorResponse + { + + public string? Details { get; set; } + public CodeErrorException(int statusCode, string? message = null, string? details = null) : base(statusCode, message) + { + + Details = details; + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorResponse.cs b/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorResponse.cs new file mode 100644 index 0000000..9ba1ef0 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Errors/CodeErrorResponse.cs @@ -0,0 +1,26 @@ +namespace CleanArchitecture.API.Errors +{ + public class CodeErrorResponse + { + public int StatusCode { get; set; } + public string? Message { get; set; } + + public CodeErrorResponse(int statusCode, string? message = null) + { + StatusCode = statusCode; + Message = message ?? GetDefaultMessageStatusCode(statusCode); + } + + private string GetDefaultMessageStatusCode(int statusCode) + { + return statusCode switch + { + 400 => "The request sent have errors", + 401 => "You are not authorized", + 404 => "Resource not found", + 500 => "An internal server error occurred", + _ => string.Empty + }; + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Middlewares/ExceptionMiddleware.cs b/CleanArchitecture/CleanArchitecture.API/Middlewares/ExceptionMiddleware.cs new file mode 100644 index 0000000..1b50462 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.API/Middlewares/ExceptionMiddleware.cs @@ -0,0 +1,62 @@ +using CleanArchitecture.API.Errors; +using CleanArchitecture.Application.Exceptions; +using System.Text.Json; + +namespace CleanArchitecture.API.Middlewares +{ + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IWebHostEnvironment _env; + + public ExceptionMiddleware(RequestDelegate next, ILogger logger, IWebHostEnvironment env) + { + _next = next; + _logger = logger; + _env = env; + } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + context.Response.ContentType = "application/json"; + var statusCode = StatusCodes.Status500InternalServerError; + var result = string.Empty; + + switch (ex) + { + case NotFoundException notFoundException: + statusCode = StatusCodes.Status404NotFound; + break; + case ValidationException validationException: + statusCode = StatusCodes.Status400BadRequest; + var validationJson = JsonSerializer.Serialize(validationException.Errors); + result = JsonSerializer.Serialize(new CodeErrorException(statusCode, ex.Message, validationJson)); + break; + case BadRequestException badRequestException: + statusCode = StatusCodes.Status400BadRequest; + break; + default: + break; + } + + if(string.IsNullOrEmpty(result)) + { + result = JsonSerializer.Serialize(new CodeErrorException(statusCode, ex.Message, ex.StackTrace)); + } + + context.Response.StatusCode = statusCode; + + await context.Response.WriteAsync(result); + + } + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.API/Program.cs b/CleanArchitecture/CleanArchitecture.API/Program.cs index 6e3fd1b..24c6b45 100644 --- a/CleanArchitecture/CleanArchitecture.API/Program.cs +++ b/CleanArchitecture/CleanArchitecture.API/Program.cs @@ -1,3 +1,8 @@ +using CleanArchitecture.Application; +using CleanArchitecture.Infrastructure; +using CleanArchitecture.Identity; +using CleanArchitecture.API.Middlewares; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -7,6 +12,20 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); + +builder.Services.AddInfrastructureServices(builder.Configuration); +builder.Services.AddApplicationServices(); +builder.Services.ConfigureIdentityServices(builder.Configuration); + +builder.Services.AddCors(o => +{ + o.AddPolicy("CorsPolicy", builder => + builder.AllowAnyOrigin(). + AllowAnyMethod(). + AllowAnyHeader()); +}); + + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -16,8 +35,13 @@ if (app.Environment.IsDevelopment()) app.UseSwaggerUI(); } +app.UseMiddleware(); + +app.UseAuthentication(); app.UseAuthorization(); +app.UseCors("CorsPolicy"); + app.MapControllers(); app.Run(); diff --git a/CleanArchitecture/CleanArchitecture.API/appsettings.json b/CleanArchitecture/CleanArchitecture.API/appsettings.json index 4d56694..7ec6057 100644 --- a/CleanArchitecture/CleanArchitecture.API/appsettings.json +++ b/CleanArchitecture/CleanArchitecture.API/appsettings.json @@ -1,4 +1,20 @@ { + + "ConnectionStrings": { + "ConnectionString": "server=localhost;database=CleanArchitecture;user=root;password=securePassword", + "IdentityConnectionString": "server=localhost;database=CleanArchitecture.Security;user=root;password=securePassword" + }, + "EmailSettings": { + "FromAddress": "alejandro@asarmiento.es", + "ApiKey": "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", + "FromName": "Alejandro Sarmiento" + }, + "JwtSettings": { + "Key": "CjF*Hp$pHvsx$%wsSyfpMevUrzj@%TJv3ZjNPk34daE7N%3KjrjCnv2V76uRY8bCtH5aduTmMwdiuh%QP3iYEh$Fy*XDzz7S&pFyyZVDLDwTdFDxrP9m#A@MBgV6oNCf", + "Issuer": "CleanArchitectureAlejandroSarmiento", + "Audience": "CleanArchitectureUsers", + "DurationInMinutes": 360 + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/CleanArchitecture/CleanArchitecture.Application/Constants/CustomClimTypes.cs b/CleanArchitecture/CleanArchitecture.Application/Constants/CustomClimTypes.cs new file mode 100644 index 0000000..935e117 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Constants/CustomClimTypes.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CleanArchitecture.Application.Constants +{ + public static class CustomClaimTypes + { + public const string Uid = "Uid"; + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Contracts/Identity/IAuthService.cs b/CleanArchitecture/CleanArchitecture.Application/Contracts/Identity/IAuthService.cs new file mode 100644 index 0000000..3cedfcc --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Contracts/Identity/IAuthService.cs @@ -0,0 +1,10 @@ +using CleanArchitecture.Application.Models.Identity; + +namespace CleanArchitecture.Application.Contracts.Identity +{ + public interface IAuthService + { + Task Login(AuthRequest request); + Task Register(RegistrationRequest request); + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Exceptions/BadRequestException.cs b/CleanArchitecture/CleanArchitecture.Application/Exceptions/BadRequestException.cs new file mode 100644 index 0000000..347362a --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Exceptions/BadRequestException.cs @@ -0,0 +1,11 @@ + +namespace CleanArchitecture.Application.Exceptions +{ + public class BadRequestException: ApplicationException + { + public BadRequestException(string message): base(message) + { + + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Exceptions/ValidationException.cs b/CleanArchitecture/CleanArchitecture.Application/Exceptions/ValidationException.cs index 36abe5b..132183c 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Exceptions/ValidationException.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Exceptions/ValidationException.cs @@ -8,10 +8,10 @@ namespace CleanArchitecture.Application.Exceptions { Errors = new Dictionary(); } - public ValidationException(IEnumerable failures): this() - { - Errors = failures.GroupBy(e=>e.PropertyName, e => e.ErrorMessage). - ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); + public ValidationException(IEnumerable failures): this() + { + Errors = failures.GroupBy(e=>e.PropertyName, e => e.ErrorMessage). + ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); } public IDictionary Errors { get; } } diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQuery.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQuery.cs index f896bf8..cc5083d 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQuery.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQuery.cs @@ -5,6 +5,6 @@ namespace CleanArchitecture.Application.Features.Videos.Queries.GetVideosList public class GetVideosListQuery(string _UserName) : IRequest> { - public string UserName { get; set; } = string.Empty; + public string UserName { get; set; } = _UserName; } } diff --git a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs index 00b5113..dfad332 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Features/Videos/Queries/GetVideosList/GetVideosListQueryHandler.cs @@ -13,7 +13,7 @@ namespace CleanArchitecture.Application.Features.Videos.Queries.GetVideosList public async Task> Handle(GetVideosListQuery request, CancellationToken cancellationToken) { - var videoList = await videoRepository.GetVideoByNombre(request.UserName); + var videoList = await videoRepository.GetVideoByUserName(request.UserName); return mapper.Map>(videoList); } } diff --git a/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs b/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs index eaf8c91..5092811 100644 --- a/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs +++ b/CleanArchitecture/CleanArchitecture.Application/Mappings/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using CleanArchitecture.Application.Features.Streamers.Commands.CreateStreamer; +using CleanArchitecture.Application.Features.Streamers.Commands.UpdateStreamer; using CleanArchitecture.Application.Features.Videos.Queries.GetVideosList; using CleanArchitecture.Domain; @@ -11,7 +12,11 @@ namespace CleanArchitecture.Application.Mappings public MappingProfile() { CreateMap(); + CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); } } } diff --git a/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthRequest.cs b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthRequest.cs new file mode 100644 index 0000000..ed2fe06 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthRequest.cs @@ -0,0 +1,8 @@ +namespace CleanArchitecture.Application.Models.Identity +{ + public class AuthRequest + { + public string Email { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthResponse.cs b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthResponse.cs new file mode 100644 index 0000000..be8cc81 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/AuthResponse.cs @@ -0,0 +1,10 @@ +namespace CleanArchitecture.Application.Models.Identity +{ + public class AuthResponse + { + public string UserId { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Models/Identity/JwtSettings.cs b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/JwtSettings.cs new file mode 100644 index 0000000..fabadfb --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/JwtSettings.cs @@ -0,0 +1,11 @@ + +namespace CleanArchitecture.Application.Models.Identity +{ + public class JwtSettings + { + public string Key { get; set; } = string.Empty; + public string Issuer { get; set; } = string.Empty; + public string Audience { get; set; } = string.Empty; + public double DurationInMinutes { get; set; } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationRequest.cs b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationRequest.cs new file mode 100644 index 0000000..d7cf045 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationRequest.cs @@ -0,0 +1,12 @@ + +namespace CleanArchitecture.Application.Models.Identity +{ + public class RegistrationRequest + { + public string Nombre { get; set; } = string.Empty; + public string Apellidos { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + } +} diff --git a/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationResponse.cs b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationResponse.cs new file mode 100644 index 0000000..c3a851c --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Application/Models/Identity/RegistrationResponse.cs @@ -0,0 +1,11 @@ + +namespace CleanArchitecture.Application.Models.Identity +{ + public class RegistrationResponse + { + public string UserId { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/CleanArchitecture.Infrastructure.csproj b/CleanArchitecture/CleanArchitecture.Data/CleanArchitecture.Infrastructure.csproj index 1eaac1c..d9755dc 100644 --- a/CleanArchitecture/CleanArchitecture.Data/CleanArchitecture.Infrastructure.csproj +++ b/CleanArchitecture/CleanArchitecture.Data/CleanArchitecture.Infrastructure.csproj @@ -17,6 +17,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/CleanArchitecture/CleanArchitecture.Data/Email/EmailService.cs b/CleanArchitecture/CleanArchitecture.Data/Email/EmailService.cs new file mode 100644 index 0000000..a30d269 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Email/EmailService.cs @@ -0,0 +1,50 @@ +using CleanArchitecture.Application.Contracts.Infrastructure; +using CleanArchitecture.Application.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SendGrid; +using SendGrid.Helpers.Mail; + +namespace CleanArchitecture.Infrastructure.Email +{ + public class EmailService : IEmailService + { + public EmailSettings emailSettings { get; } + public ILogger logger { get; } + + public EmailService(IOptions _emailSettings, ILogger _logger) + { + emailSettings = _emailSettings.Value; + logger = _logger; + } + + public async Task SendEmail(Application.Models.Email email) + { + var client = new SendGridClient(emailSettings.ApiKey); + + var subject = email.Subject; + var to = new EmailAddress(email.To); + var emailBody = email.Body; + + var from = new EmailAddress + { + Email = emailSettings.FromAddress, + Name = emailSettings.FromName + }; + + var sendGridMessage = MailHelper.CreateSingleEmail(from, to, subject, emailBody, emailBody); + var response = await client.SendEmailAsync(sendGridMessage); + + if(response.StatusCode == System.Net.HttpStatusCode.Accepted || response.StatusCode == System.Net.HttpStatusCode.OK) { + return true; + } + else + { + logger.LogError($"The email can not been send"); + return false; + } + + + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs b/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs new file mode 100644 index 0000000..afa138f --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/InfrastructureServiceRegistration.cs @@ -0,0 +1,35 @@ +using CleanArchitecture.Application.Contracts.Infrastructure; +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Application.Models; +using CleanArchitecture.Infrastructure.Email; +using CleanArchitecture.Infrastructure.Persistence; +using CleanArchitecture.Infrastructure.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace CleanArchitecture.Infrastructure +{ + public static class InfrastructureServiceRegistration + { + + public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) + { + var dbConnectionString = configuration.GetConnectionString("ConnectionString"); + services.AddDbContext(options => + + options.UseMySql(dbConnectionString, ServerVersion.AutoDetect(dbConnectionString)) + .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Command.Name }, Microsoft.Extensions.Logging.LogLevel.Information) + ); + + services.AddScoped(typeof(IAsyncRepository<>), typeof(RepositoryBase<>)); + services.AddScoped(); + services.AddScoped(); + + services.Configure(c =>configuration.GetSection("EmailSettings")); + services.AddTransient(); + + return services; + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.Designer.cs b/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.Designer.cs new file mode 100644 index 0000000..8757082 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.Designer.cs @@ -0,0 +1,230 @@ +// +using System; +using CleanArchitecture.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CleanArchitecture.Data.Migrations +{ + [DbContext(typeof(StreamerDbContext))] + [Migration("20240218123327_cleanArchitectureFromApi")] + partial class cleanArchitectureFromApi + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("CleanArchitecture.Domain.Actor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Apellido") + .HasColumnType("longtext"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + + b.Property("Nombre") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Actor"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Director", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Apellido") + .HasColumnType("longtext"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + + b.Property("Nombre") + .HasColumnType("longtext"); + + b.Property("VideoId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Director"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Streamer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + + b.Property("Nombre") + .HasColumnType("longtext"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Streamers"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Video", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("DirectorId") + .HasColumnType("int"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + + b.Property("Nombre") + .HasColumnType("longtext"); + + b.Property("StreamerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DirectorId") + .IsUnique(); + + b.HasIndex("StreamerId"); + + b.ToTable("Videos"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.VideoActor", b => + { + b.Property("VideoId") + .HasColumnType("int"); + + b.Property("ActorId") + .HasColumnType("int"); + + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("int"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("VideoId", "ActorId"); + + b.HasIndex("ActorId"); + + b.ToTable("VideoActor"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Video", b => + { + b.HasOne("CleanArchitecture.Domain.Director", "Director") + .WithOne("Video") + .HasForeignKey("CleanArchitecture.Domain.Video", "DirectorId"); + + b.HasOne("CleanArchitecture.Domain.Streamer", "Streamer") + .WithMany("Videos") + .HasForeignKey("StreamerId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Director"); + + b.Navigation("Streamer"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.VideoActor", b => + { + b.HasOne("CleanArchitecture.Domain.Actor", "Actor") + .WithMany() + .HasForeignKey("ActorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CleanArchitecture.Domain.Video", "Video") + .WithMany() + .HasForeignKey("VideoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Actor"); + + b.Navigation("Video"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Director", b => + { + b.Navigation("Video"); + }); + + modelBuilder.Entity("CleanArchitecture.Domain.Streamer", b => + { + b.Navigation("Videos"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.cs b/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.cs new file mode 100644 index 0000000..6ff4d37 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Migrations/20240218123327_cleanArchitectureFromApi.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CleanArchitecture.Data.Migrations +{ + /// + public partial class cleanArchitectureFromApi : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CreatedBy", + table: "VideoActor", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "CreatedDate", + table: "VideoActor", + type: "datetime(6)", + nullable: true); + + migrationBuilder.AddColumn( + name: "Id", + table: "VideoActor", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "LastModifiedBy", + table: "VideoActor", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "LastModifiedDate", + table: "VideoActor", + type: "datetime(6)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CreatedBy", + table: "VideoActor"); + + migrationBuilder.DropColumn( + name: "CreatedDate", + table: "VideoActor"); + + migrationBuilder.DropColumn( + name: "Id", + table: "VideoActor"); + + migrationBuilder.DropColumn( + name: "LastModifiedBy", + table: "VideoActor"); + + migrationBuilder.DropColumn( + name: "LastModifiedDate", + table: "VideoActor"); + } + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Migrations/ApplicationDbContextModelSnapshot.cs b/CleanArchitecture/CleanArchitecture.Data/Migrations/ApplicationDbContextModelSnapshot.cs index 644f739..9a1baf8 100644 --- a/CleanArchitecture/CleanArchitecture.Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/CleanArchitecture/CleanArchitecture.Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -154,6 +154,21 @@ namespace CleanArchitecture.Data.Migrations b.Property("ActorId") .HasColumnType("int"); + b.Property("CreatedBy") + .HasColumnType("longtext"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Id") + .HasColumnType("int"); + + b.Property("LastModifiedBy") + .HasColumnType("longtext"); + + b.Property("LastModifiedDate") + .HasColumnType("datetime(6)"); + b.HasKey("VideoId", "ActorId"); b.HasIndex("ActorId"); diff --git a/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs b/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs index e3f4ee8..b0eefe1 100644 --- a/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs +++ b/CleanArchitecture/CleanArchitecture.Data/Repositories/RepositoryBase.cs @@ -9,9 +9,9 @@ namespace CleanArchitecture.Infrastructure.Repositories public class RepositoryBase : IAsyncRepository where T : BaseDomainModel { protected readonly StreamerDbContext context; - public RepositoryBase(StreamerDbContext _context) - { - context = _context; + public RepositoryBase(StreamerDbContext _context) + { + context = _context; } public async Task> GetAllAsync() @@ -48,7 +48,7 @@ namespace CleanArchitecture.Infrastructure.Repositories { IQueryable query = context.Set(); if (disableTracking) query = query.AsNoTracking(); - if (includes != null) query = includes.Aggregate(query, (current, include) => current.Include(include); + if (includes != null) query = includes.Aggregate(query, (current, include) => current.Include(include)); if(predicate != null) query = query.Where(predicate); if (orderBy!=null) return await orderBy(query).ToListAsync(); diff --git a/CleanArchitecture/CleanArchitecture.Data/Repositories/StreamerRepository.cs b/CleanArchitecture/CleanArchitecture.Data/Repositories/StreamerRepository.cs new file mode 100644 index 0000000..de544ad --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Repositories/StreamerRepository.cs @@ -0,0 +1,17 @@ +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Domain; +using CleanArchitecture.Infrastructure.Persistence; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CleanArchitecture.Infrastructure.Repositories +{ + public class StreamerRepository: RepositoryBase, IStreamerRepository + { + public StreamerRepository(StreamerDbContext context) : base(context) { } + + } +} diff --git a/CleanArchitecture/CleanArchitecture.Data/Repositories/VideoRepository.cs b/CleanArchitecture/CleanArchitecture.Data/Repositories/VideoRepository.cs new file mode 100644 index 0000000..bfd8ce9 --- /dev/null +++ b/CleanArchitecture/CleanArchitecture.Data/Repositories/VideoRepository.cs @@ -0,0 +1,23 @@ +using CleanArchitecture.Application.Contracts.Persistence; +using CleanArchitecture.Domain; +using CleanArchitecture.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace CleanArchitecture.Infrastructure.Repositories +{ + internal class VideoRepository : RepositoryBase