Merge branch 'dev'

This commit is contained in:
Alejandro Sarmiento
2024-02-20 01:32:54 +01:00
43 changed files with 2348 additions and 16 deletions

View File

@@ -7,12 +7,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
<ProjectReference Include="..\CleanArchitecture.Data\CleanArchitecture.Infrastructure.csproj" />
<ProjectReference Include="..\CleanArchitecture.Identity\CleanArchitecture.Identity.csproj" />
</ItemGroup>
</Project>

View File

@@ -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<ActionResult<AuthResponse>> Login([FromBody] AuthRequest request)
{
var response = await authService.Login(request);
return Ok(response);
}
[HttpPost("register")]
public async Task<ActionResult<RegistrationResponse>> Register([FromBody] RegistrationRequest request)
{
var response = await authService.Register(request);
return Ok(response);
}
}
}

View File

@@ -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<ActionResult<int>> 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]

View File

@@ -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<VideosVm>), (int)HttpStatusCode.OK)]
public async Task<ActionResult<IEnumerable<VideosVm>>> GetVideosByUserName(string username)
{

View File

@@ -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;
}
}
}

View File

@@ -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
};
}
}
}

View File

@@ -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<ExceptionMiddleware> _logger;
private readonly IWebHostEnvironment _env;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> 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);
}
}
}
}

View File

@@ -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<ExceptionMiddleware>();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("CorsPolicy");
app.MapControllers();
app.Run();

View File

@@ -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",

View File

@@ -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";
}
}

View File

@@ -0,0 +1,10 @@
using CleanArchitecture.Application.Models.Identity;
namespace CleanArchitecture.Application.Contracts.Identity
{
public interface IAuthService
{
Task<AuthResponse> Login(AuthRequest request);
Task<RegistrationResponse> Register(RegistrationRequest request);
}
}

View File

@@ -0,0 +1,11 @@

namespace CleanArchitecture.Application.Exceptions
{
public class BadRequestException: ApplicationException
{
public BadRequestException(string message): base(message)
{
}
}
}

View File

@@ -8,10 +8,10 @@ namespace CleanArchitecture.Application.Exceptions
{
Errors = new Dictionary<string, string[]>();
}
public ValidationException(IEnumerable<ValidationFailure> failures): this()
{
Errors = failures.GroupBy(e=>e.PropertyName, e => e.ErrorMessage).
ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
public ValidationException(IEnumerable<ValidationFailure> failures): this()
{
Errors = failures.GroupBy(e=>e.PropertyName, e => e.ErrorMessage).
ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}
public IDictionary<string, string[]> Errors { get; }
}

View File

@@ -5,6 +5,6 @@ namespace CleanArchitecture.Application.Features.Videos.Queries.GetVideosList
public class GetVideosListQuery(string _UserName) :
IRequest<List<VideosVm>>
{
public string UserName { get; set; } = string.Empty;
public string UserName { get; set; } = _UserName;
}
}

View File

@@ -13,7 +13,7 @@ namespace CleanArchitecture.Application.Features.Videos.Queries.GetVideosList
public async Task<List<VideosVm>> Handle(GetVideosListQuery request, CancellationToken cancellationToken)
{
var videoList = await videoRepository.GetVideoByNombre(request.UserName);
var videoList = await videoRepository.GetVideoByUserName(request.UserName);
return mapper.Map<List<VideosVm>>(videoList);
}
}

View File

@@ -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<Video, VideosVm>();
CreateMap<VideosVm, Video>();
CreateMap<CreateStreamerCommand, Streamer>();
CreateMap<Streamer, CreateStreamerCommand>();
CreateMap<UpdateStreamerCommand, Streamer>();
CreateMap<Streamer, UpdateStreamerCommand>();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -17,6 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
<PackageReference Include="SendGrid" Version="9.29.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -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<EmailService> logger { get; }
public EmailService(IOptions<EmailSettings> _emailSettings, ILogger<EmailService> _logger)
{
emailSettings = _emailSettings.Value;
logger = _logger;
}
public async Task<bool> 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;
}
}
}
}

View File

@@ -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<StreamerDbContext>(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<IVideoRepository, VideoRepository>();
services.AddScoped<IStreamerRepository, StreamerRepository>();
services.Configure<EmailSettings>(c =>configuration.GetSection("EmailSettings"));
services.AddTransient<IEmailService, EmailService>();
return services;
}
}
}

View File

@@ -0,0 +1,230 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Apellido")
.HasColumnType("longtext");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("LastModifiedDate")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Actor");
});
modelBuilder.Entity("CleanArchitecture.Domain.Director", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Apellido")
.HasColumnType("longtext");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("LastModifiedDate")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.HasColumnType("longtext");
b.Property<int>("VideoId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("Director");
});
modelBuilder.Entity("CleanArchitecture.Domain.Streamer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("LastModifiedDate")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.HasColumnType("longtext");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Streamers");
});
modelBuilder.Entity("CleanArchitecture.Domain.Video", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<int?>("DirectorId")
.HasColumnType("int");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("LastModifiedDate")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.HasColumnType("longtext");
b.Property<int>("StreamerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("DirectorId")
.IsUnique();
b.HasIndex("StreamerId");
b.ToTable("Videos");
});
modelBuilder.Entity("CleanArchitecture.Domain.VideoActor", b =>
{
b.Property<int>("VideoId")
.HasColumnType("int");
b.Property<int>("ActorId")
.HasColumnType("int");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("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
}
}
}

View File

@@ -0,0 +1,72 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CleanArchitecture.Data.Migrations
{
/// <inheritdoc />
public partial class cleanArchitectureFromApi : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "CreatedBy",
table: "VideoActor",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDate",
table: "VideoActor",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "Id",
table: "VideoActor",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<string>(
name: "LastModifiedBy",
table: "VideoActor",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<DateTime>(
name: "LastModifiedDate",
table: "VideoActor",
type: "datetime(6)",
nullable: true);
}
/// <inheritdoc />
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");
}
}
}

View File

@@ -154,6 +154,21 @@ namespace CleanArchitecture.Data.Migrations
b.Property<int>("ActorId")
.HasColumnType("int");
b.Property<string>("CreatedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime(6)");
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("LastModifiedBy")
.HasColumnType("longtext");
b.Property<DateTime?>("LastModifiedDate")
.HasColumnType("datetime(6)");
b.HasKey("VideoId", "ActorId");
b.HasIndex("ActorId");

View File

@@ -9,9 +9,9 @@ namespace CleanArchitecture.Infrastructure.Repositories
public class RepositoryBase<T> : IAsyncRepository<T> where T : BaseDomainModel
{
protected readonly StreamerDbContext context;
public RepositoryBase(StreamerDbContext _context)
{
context = _context;
public RepositoryBase(StreamerDbContext _context)
{
context = _context;
}
public async Task<IReadOnlyList<T>> GetAllAsync()
@@ -48,7 +48,7 @@ namespace CleanArchitecture.Infrastructure.Repositories
{
IQueryable<T> query = context.Set<T>();
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();

View File

@@ -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<Streamer>, IStreamerRepository
{
public StreamerRepository(StreamerDbContext context) : base(context) { }
}
}

View File

@@ -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<Video>, IVideoRepository
{
public VideoRepository(StreamerDbContext context) : base(context) { }
public async Task<Video> GetVideoByNombre(string nombreVideo)
{
return await context.Videos.Where(x => x.Nombre!.Equals(nombreVideo)).SingleOrDefaultAsync();
}
public async Task<IEnumerable<Video>> GetVideoByUserName(string userName)
{
return await context.Videos.Where(x => x.CreatedBy!.Equals(userName)).ToListAsync();
}
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using CleanArchitecture.Identity.Configurations;
using CleanArchitecture.Identity.Models;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace CleanArchitecture.Identity
{
public class CleanArchitectureIdentityDbContext : IdentityDbContext<ApplicationUser>
{
public CleanArchitectureIdentityDbContext(DbContextOptions<CleanArchitectureIdentityDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new RoleConfiguration());
builder.ApplyConfiguration(new UserConfiguration());
builder.ApplyConfiguration(new UserRoleConfiguration());
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace CleanArchitecture.Identity.Configurations
{
public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole>
{
public void Configure(EntityTypeBuilder<IdentityRole> builder)
{
builder.HasData(
new IdentityRole
{
Id = "26f31fc8-f911-4761-956f-e72c0041c9c9",
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
},
new IdentityRole
{
Id = "580b6053-a9a1-4373-8081-b1ae5db82406",
Name = "Operator",
NormalizedName = "OPERATOR"
}
);
}
}
}

View File

@@ -0,0 +1,43 @@
using CleanArchitecture.Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace CleanArchitecture.Identity.Configurations
{
public class UserConfiguration : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
var hasher = new PasswordHasher<ApplicationUser>();
builder.HasData(
new ApplicationUser()
{
Id = "b6e00d83-b0e5-4ec3-8935-c52857fc5678",
Email = "admin@asarmiento.es",
NormalizedEmail = "ADMIN@ASARMIENTO.ES",
Nombre = "Admin",
Apellidos = "Asarmiento",
UserName = "Administrator",
NormalizedUserName = "ADMINISTRATOR",
PasswordHash = hasher.HashPassword(null, "Password?2024"),
EmailConfirmed = true,
},
new ApplicationUser()
{
Id = "611f8061-1c0f-4908-9d20-cb4fc0c18c74",
Email = "alex@asarmiento.es",
NormalizedEmail = "ALEX@ASARMIENTO.ES",
Nombre = "Alex",
Apellidos = "Sarmiento",
UserName = "Alex",
NormalizedUserName = "ALEX",
PasswordHash = hasher.HashPassword(null, "Password?2024"),
EmailConfirmed = true,
}
);
}
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CleanArchitecture.Identity.Configurations
{
public class UserRoleConfiguration : IEntityTypeConfiguration<IdentityUserRole<string>>
{
public void Configure(EntityTypeBuilder<IdentityUserRole<string>> builder)
{
builder.HasData
(
new IdentityUserRole<string>
{
RoleId = "26f31fc8-f911-4761-956f-e72c0041c9c9",
UserId = "b6e00d83-b0e5-4ec3-8935-c52857fc5678"
},
new IdentityUserRole<string>
{
RoleId = "580b6053-a9a1-4373-8081-b1ae5db82406",
UserId = "611f8061-1c0f-4908-9d20-cb4fc0c18c74"
}
);
}
}
}

View File

@@ -0,0 +1,51 @@
using CleanArchitecture.Application.Contracts.Identity;
using CleanArchitecture.Application.Models.Identity;
using CleanArchitecture.Identity.Models;
using CleanArchitecture.Identity.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace CleanArchitecture.Identity
{
public static class IdentityServiceRegistration
{
public static IServiceCollection ConfigureIdentityServices(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
var dbConnectionString = configuration.GetConnectionString("IdentityConnectionString");
services.AddDbContext<CleanArchitectureIdentityDbContext>(options =>
options.UseMySql(dbConnectionString, ServerVersion.AutoDetect(dbConnectionString),
b => b.MigrationsAssembly(typeof(CleanArchitectureIdentityDbContext).Assembly.FullName)));
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<CleanArchitectureIdentityDbContext>().AddDefaultTokenProviders();
services.AddTransient<IAuthService, AuthService>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
ValidIssuer = configuration["JwtSettings:Issuer"],
ValidAudience = configuration["JwtSettings:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JwtSettings:Key"]!))
};
});
return services;
}
}
}

View File

@@ -0,0 +1,342 @@
// <auto-generated />
using System;
using CleanArchitecture.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Identity.Migrations
{
[DbContext(typeof(CleanArchitectureIdentityDbContext))]
[Migration("20240219191942_GenerateTables")]
partial class GenerateTables
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("CleanArchitecture.Identity.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Apellidos")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
b.HasData(
new
{
Id = "b6e00d83-b0e5-4ec3-8935-c52857fc5678",
AccessFailedCount = 0,
Apellidos = "Asarmiento",
ConcurrencyStamp = "97e3fb26-daef-4b7f-b6b1-8c04c3eacd32",
Email = "admin@asarmiento.es",
EmailConfirmed = true,
LockoutEnabled = false,
Nombre = "Admin",
NormalizedEmail = "ADMIN@ASARMIENTO.ES",
NormalizedUserName = "ADMINISTRATOR",
PasswordHash = "AQAAAAIAAYagAAAAEPX34AbtW+GncoqvoZXfeSGe8EOMCiJQzJCgXt3ssCEixBsAPbESWZPw4tDiUyOn0Q==",
PhoneNumberConfirmed = false,
SecurityStamp = "df3ab37a-f3ff-41df-9cdf-0277a3f3de62",
TwoFactorEnabled = false,
UserName = "Administrator"
},
new
{
Id = "611f8061-1c0f-4908-9d20-cb4fc0c18c74",
AccessFailedCount = 0,
Apellidos = "Sarmiento",
ConcurrencyStamp = "d15b9b26-3136-49a8-9356-2bbae70bdaa9",
Email = "alex@asarmiento.es",
EmailConfirmed = true,
LockoutEnabled = false,
Nombre = "Alex",
NormalizedEmail = "ALEX@ASARMIENTO.ES",
NormalizedUserName = "ALEX",
PasswordHash = "AQAAAAIAAYagAAAAEA6sbbWSTta0J9ysbipB1GNhwMMNdxqNWz3wTsuwOA/DsRe/DLihxhwsVOU1ECxlVg==",
PhoneNumberConfirmed = false,
SecurityStamp = "f1e4038d-7cde-4548-8ab1-366ec3b3267f",
TwoFactorEnabled = false,
UserName = "Alex"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
b.HasData(
new
{
Id = "26f31fc8-f911-4761-956f-e72c0041c9c9",
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
},
new
{
Id = "580b6053-a9a1-4373-8081-b1ae5db82406",
Name = "Operator",
NormalizedName = "OPERATOR"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
b.HasData(
new
{
UserId = "b6e00d83-b0e5-4ec3-8935-c52857fc5678",
RoleId = "26f31fc8-f911-4761-956f-e72c0041c9c9"
},
new
{
UserId = "611f8061-1c0f-4908-9d20-cb4fc0c18c74",
RoleId = "580b6053-a9a1-4373-8081-b1ae5db82406"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,295 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace CleanArchitecture.Identity.Migrations
{
/// <inheritdoc />
public partial class GenerateTables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Name = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Nombre = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Apellidos = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
UserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedUserName = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Email = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
NormalizedEmail = table.Column<string>(type: "varchar(256)", maxLength: 256, nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
EmailConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
PasswordHash = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
SecurityStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ConcurrencyStamp = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PhoneNumber = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
PhoneNumberConfirmed = table.Column<bool>(type: "tinyint(1)", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
LockoutEnabled = table.Column<bool>(type: "tinyint(1)", nullable: false),
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimValue = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimType = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ClaimValue = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ProviderKey = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ProviderDisplayName = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
RoleId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
LoginProvider = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Name = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Value = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.InsertData(
table: "AspNetRoles",
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
values: new object[,]
{
{ "26f31fc8-f911-4761-956f-e72c0041c9c9", null, "Administrator", "ADMINISTRATOR" },
{ "580b6053-a9a1-4373-8081-b1ae5db82406", null, "Operator", "OPERATOR" }
});
migrationBuilder.InsertData(
table: "AspNetUsers",
columns: new[] { "Id", "AccessFailedCount", "Apellidos", "ConcurrencyStamp", "Email", "EmailConfirmed", "LockoutEnabled", "LockoutEnd", "Nombre", "NormalizedEmail", "NormalizedUserName", "PasswordHash", "PhoneNumber", "PhoneNumberConfirmed", "SecurityStamp", "TwoFactorEnabled", "UserName" },
values: new object[,]
{
{ "611f8061-1c0f-4908-9d20-cb4fc0c18c74", 0, "Sarmiento", "d15b9b26-3136-49a8-9356-2bbae70bdaa9", "alex@asarmiento.es", true, false, null, "Alex", "ALEX@ASARMIENTO.ES", "ALEX", "AQAAAAIAAYagAAAAEA6sbbWSTta0J9ysbipB1GNhwMMNdxqNWz3wTsuwOA/DsRe/DLihxhwsVOU1ECxlVg==", null, false, "f1e4038d-7cde-4548-8ab1-366ec3b3267f", false, "Alex" },
{ "b6e00d83-b0e5-4ec3-8935-c52857fc5678", 0, "Asarmiento", "97e3fb26-daef-4b7f-b6b1-8c04c3eacd32", "admin@asarmiento.es", true, false, null, "Admin", "ADMIN@ASARMIENTO.ES", "ADMINISTRATOR", "AQAAAAIAAYagAAAAEPX34AbtW+GncoqvoZXfeSGe8EOMCiJQzJCgXt3ssCEixBsAPbESWZPw4tDiUyOn0Q==", null, false, "df3ab37a-f3ff-41df-9cdf-0277a3f3de62", false, "Administrator" }
});
migrationBuilder.InsertData(
table: "AspNetUserRoles",
columns: new[] { "RoleId", "UserId" },
values: new object[,]
{
{ "580b6053-a9a1-4373-8081-b1ae5db82406", "611f8061-1c0f-4908-9d20-cb4fc0c18c74" },
{ "26f31fc8-f911-4761-956f-e72c0041c9c9", "b6e00d83-b0e5-4ec3-8935-c52857fc5678" }
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@@ -0,0 +1,339 @@
// <auto-generated />
using System;
using CleanArchitecture.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Identity.Migrations
{
[DbContext(typeof(CleanArchitectureIdentityDbContext))]
partial class CleanArchitectureIdentityDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("CleanArchitecture.Identity.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("Apellidos")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("Nombre")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
b.HasData(
new
{
Id = "b6e00d83-b0e5-4ec3-8935-c52857fc5678",
AccessFailedCount = 0,
Apellidos = "Asarmiento",
ConcurrencyStamp = "97e3fb26-daef-4b7f-b6b1-8c04c3eacd32",
Email = "admin@asarmiento.es",
EmailConfirmed = true,
LockoutEnabled = false,
Nombre = "Admin",
NormalizedEmail = "ADMIN@ASARMIENTO.ES",
NormalizedUserName = "ADMINISTRATOR",
PasswordHash = "AQAAAAIAAYagAAAAEPX34AbtW+GncoqvoZXfeSGe8EOMCiJQzJCgXt3ssCEixBsAPbESWZPw4tDiUyOn0Q==",
PhoneNumberConfirmed = false,
SecurityStamp = "df3ab37a-f3ff-41df-9cdf-0277a3f3de62",
TwoFactorEnabled = false,
UserName = "Administrator"
},
new
{
Id = "611f8061-1c0f-4908-9d20-cb4fc0c18c74",
AccessFailedCount = 0,
Apellidos = "Sarmiento",
ConcurrencyStamp = "d15b9b26-3136-49a8-9356-2bbae70bdaa9",
Email = "alex@asarmiento.es",
EmailConfirmed = true,
LockoutEnabled = false,
Nombre = "Alex",
NormalizedEmail = "ALEX@ASARMIENTO.ES",
NormalizedUserName = "ALEX",
PasswordHash = "AQAAAAIAAYagAAAAEA6sbbWSTta0J9ysbipB1GNhwMMNdxqNWz3wTsuwOA/DsRe/DLihxhwsVOU1ECxlVg==",
PhoneNumberConfirmed = false,
SecurityStamp = "f1e4038d-7cde-4548-8ab1-366ec3b3267f",
TwoFactorEnabled = false,
UserName = "Alex"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
b.HasData(
new
{
Id = "26f31fc8-f911-4761-956f-e72c0041c9c9",
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
},
new
{
Id = "580b6053-a9a1-4373-8081-b1ae5db82406",
Name = "Operator",
NormalizedName = "OPERATOR"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
b.HasData(
new
{
UserId = "b6e00d83-b0e5-4ec3-8935-c52857fc5678",
RoleId = "26f31fc8-f911-4761-956f-e72c0041c9c9"
},
new
{
UserId = "611f8061-1c0f-4908-9d20-cb4fc0c18c74",
RoleId = "580b6053-a9a1-4373-8081-b1ae5db82406"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("CleanArchitecture.Identity.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Identity;
namespace CleanArchitecture.Identity.Models
{
public class ApplicationUser : IdentityUser
{
public string Nombre { get; set; } = string.Empty;
public string Apellidos { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,120 @@
using CleanArchitecture.Application.Constants;
using CleanArchitecture.Application.Contracts.Identity;
using CleanArchitecture.Application.Models.Identity;
using CleanArchitecture.Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace CleanArchitecture.Identity.Services
{
public class AuthService : IAuthService
{
private readonly UserManager<ApplicationUser> userManager;
private readonly SignInManager<ApplicationUser> signInManager;
private readonly JwtSettings jwtSettings;
public AuthService(UserManager<ApplicationUser> _userManager, SignInManager<ApplicationUser> _signInManager, IOptions<JwtSettings> _jwtSettings)
{
userManager = _userManager;
signInManager = _signInManager;
jwtSettings = _jwtSettings.Value;
}
public async Task<AuthResponse> Login(AuthRequest request)
{
var user = await userManager.FindByEmailAsync(request.Email);
if (user == null)
{
throw new Exception($"User with email: {request.Email} does not exist");
}
var result = await signInManager.CheckPasswordSignInAsync(user, request.Password, false);
if (!result.Succeeded)
{
throw new Exception("Invalid Login Attempt");
}
var token = await GenerateToken(user);
var authResponse = new AuthResponse
{
UserId = user.Id,
Token = new JwtSecurityTokenHandler().WriteToken(token),
Email = user.Email!,
Username = user.UserName!
};
return authResponse;
}
public async Task<RegistrationResponse> Register(RegistrationRequest request)
{
var existingUser = await userManager.FindByEmailAsync(request.Email);
if (existingUser != null)
{
throw new Exception($"User with email: {request.Email} already exists");
}
var existingEmail = await userManager.FindByEmailAsync(request.Email);
if (existingEmail != null)
{
throw new Exception($"Email: {request.Email} is already in use");
}
var newUser = new ApplicationUser
{
Nombre = request.Nombre,
Apellidos = request.Apellidos,
Email = request.Email,
UserName = request.Username
};
var result = await userManager.CreateAsync(newUser, request.Password);
if (!result.Succeeded)
{
var errors = string.Empty;
foreach (var error in result.Errors)
{
errors += $"{error.Description}, ";
}
throw new Exception($"User Registration has failed: {errors}");
}
var token = await GenerateToken(newUser);
var registrationResponse = new RegistrationResponse
{
UserId = newUser.Id,
Email = newUser.Email,
Token = new JwtSecurityTokenHandler().WriteToken(token),
Username = newUser.UserName
};
return registrationResponse;
}
private async Task<JwtSecurityToken> GenerateToken(ApplicationUser user)
{
var userClaims = await userManager.GetClaimsAsync(user);
var roles = await userManager.GetRolesAsync(user);
var roleClaims = roles.Select(role => new Claim(ClaimTypes.Role, role));
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName!),
new Claim(JwtRegisteredClaimNames.Email, user.Email!),
new Claim(CustomClaimTypes.Uid, user.Id),
}.Union(userClaims).Union(roleClaims);
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: jwtSettings.Issuer,
audience: jwtSettings.Audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(jwtSettings.DurationInMinutes),
signingCredentials: signingCredentials);
return token;
}
}
}

View File

@@ -19,9 +19,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{8BFA0E48-D
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{39B5751D-BEC9-4C98-9183-D5AE52248ABA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.API", "CleanArchitecture.API\CleanArchitecture.API.csproj", "{4B1B4D47-84CF-4CAA-9A0A-69D8AC16463E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CleanArchitecture.API", "CleanArchitecture.API\CleanArchitecture.API.csproj", "{4B1B4D47-84CF-4CAA-9A0A-69D8AC16463E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Application", "CleanArchitecture.Application\CleanArchitecture.Application.csproj", "{B9B096A1-C916-40DF-9F9F-9CF384CC0398}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CleanArchitecture.Application", "CleanArchitecture.Application\CleanArchitecture.Application.csproj", "{B9B096A1-C916-40DF-9F9F-9CF384CC0398}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitecture.Identity", "CleanArchitecture.Identity\CleanArchitecture.Identity.csproj", "{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -45,6 +47,10 @@ Global
{B9B096A1-C916-40DF-9F9F-9CF384CC0398}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9B096A1-C916-40DF-9F9F-9CF384CC0398}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9B096A1-C916-40DF-9F9F-9CF384CC0398}.Release|Any CPU.Build.0 = Release|Any CPU
{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -57,6 +63,7 @@ Global
{39B5751D-BEC9-4C98-9183-D5AE52248ABA} = {D123D836-8F81-46F5-A58F-E7FB09105777}
{4B1B4D47-84CF-4CAA-9A0A-69D8AC16463E} = {6EB52FD0-D3E0-4D88-BD5B-424229B76BD1}
{B9B096A1-C916-40DF-9F9F-9CF384CC0398} = {8BFA0E48-D4BE-48A4-B625-7FD3C7CBC88F}
{2D1CFE96-C41D-4EFC-9DBA-10249988D9FF} = {39B5751D-BEC9-4C98-9183-D5AE52248ABA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1D95339D-3ACA-48AD-B094-1C90F6DB327B}

View File

@@ -0,0 +1,292 @@
{
"info": {
"_postman_id": "a820e2ad-6884-4bf0-af53-d797d4459388",
"name": "API Documentation #reference",
"description": "This template contains a boilerplate for documentation that you can quickly customize and reuse.\n\n### How to use this template:\n\n- Replace the content given brackets (()) with your API's details.\n- Tips are formatted in `codespan` - feel free to read and remove them.\n \n\n---\n\n`Start with a brief overview of what your API offers.`\n\nThe ((product name)) provides many API products, tools, and resources that enable you to ((add product value here)).\n\n`You can also list the APIs you offer, link to the relevant pages, or do both in this section.`\n\n## **Getting started guide**\n\n`List the steps or points required to start using your APIs. Make sure to cover everything required to reach success with your API as quickly as possible.`\n\nTo start using the ((add APIs here)), you need to -\n\n`The points given below are from The Postman API's documentation. You can reference it to write your own getting started guide.`\n\n- You must use a valid API Key to send requests to the API endpoints. You can get your API key from Postman's [integrations dashboard](https://go.postman.co/settings/me/api-keys).\n- The API has [rate and usage limits](https://postman.postman.co/workspace/Collection-Templates~6311738d-2e70-441f-ae12-78caf078c5b7/collection/22517504-e9c28f47-1253-44af-a2f3-20dce4da1f18?ctx=documentation#rate-and-usage-limits).\n- The API only responds to HTTPS-secured communications. Any requests sent via HTTP return an HTTP 301 redirect to the corresponding HTTPS resources.\n- The API returns request responses in JSON format. When an API request returns an error, it is sent in the JSON response as an error key.\n \n\n## Authentication\n\n`Add details on the authorization keys/tokens required, steps that cover how to get them, and the relevant error codes.`\n\nThe ((product name)) API uses ((add your API's authorization type)) for authentication.\n\n`The details given below are from the Postman API's documentation. You can reference it to write your own authentication section.`\n\nPostman uses API keys for authentication. You can generate a Postman API key in the [API keys](https://postman.postman.co/settings/me/api-keys) section of your Postman account settings.\n\nYou must include an API key in each request to the Postman API with the X-Api-Key request header.\n\n### Authentication error response\n\nIf an API key is missing, malformed, or invalid, you will receive an HTTP 401 Unauthorized response code.\n\n## Rate and usage limits\n\n`Use this section to cover your APIs' terms of use. Include API limits, constraints, and relevant error codes, so consumers understand the permitted API usage and practices.`\n\n`The example given below is from The Postman API's documentation. Use it as a reference to write your APIs' terms of use.`\n\nAPI access rate limits apply at a per-API key basis in unit time. The limit is 300 requests per minute. Also, depending on your plan, you may have usage limits. If you exceed either limit, your request will return an HTTP 429 Too Many Requests status code.\n\nEach API response returns the following set of headers to help you identify your use status:\n\n| Header | Description |\n| --- | --- |\n| `X-RateLimit-Limit` | The maximum number of requests that the consumer is permitted to make per minute. |\n| `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window. |\n| `X-RateLimit-Reset` | The time at which the current rate limit window resets in UTC epoch seconds. |\n\n### 503 response\n\nAn HTTP `503` response from our servers indicates there is an unexpected spike in API access traffic. The server is usually operational within the next five minutes. If the outage persists or you receive any other form of an HTTP `5XX` error, [contact support](https://support.postman.com/hc/en-us/requests/new/).\n\n### **Need some help?**\n\n`Add links that customers can refer to whenever they need help.`\n\nIn case you have questions, go through our tutorials ((link to your video or help documentation here)). Or visit our FAQ page ((link to the relevant page)).\n\nOr you can check out our community forum, theres a good chance our community has an answer for you. Visit our developer forum ((link to developer forum)) to review topics, ask questions, and learn from others.\n\n`You can also document or add links to libraries, code examples, and other resources needed to make a request.`",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "33066714"
},
"item": [
{
"name": "User",
"item": [
{
"name": "Login_Administrator",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var responseData = pm.response.json();\r",
"pm.environment.set(\"PostLoginToken\", responseData.token);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"email\":\"{{EmailAdministrator}}\",\r\n \"password\":\"{{PasswordAdministrator}}\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{UrlBase}}{{UrlAccount}}login",
"host": [
"{{UrlBase}}{{UrlAccount}}login"
]
}
},
"response": []
},
{
"name": "Login_Operator",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var responseData = pm.response.json();\r",
"pm.environment.set(\"PostLoginToken\", responseData.token);"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"email\":\"{{EmailOperator}}\",\r\n \"password\":\"{{PasswordOperator}}\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{UrlBase}}{{UrlAccount}}login",
"host": [
"{{UrlBase}}{{UrlAccount}}login"
]
}
},
"response": []
}
],
"description": "The `/me` endpoints let you manage information about the authenticated user."
},
{
"name": "Collections",
"item": [
{
"name": "Account_Controller",
"item": [
{
"name": "Register_User",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"nombre\": \"Nombre\",\r\n \"apellidos\": \"Apellidos\",\r\n \"email\": \"nombre.apellidos@asarmiento.es\",\r\n \"username\": \"nombreapellidos\",\r\n \"password\": \"{{PasswordOperator}}\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{UrlBase}}{{UrlAccount}}register",
"host": [
"{{UrlBase}}{{UrlAccount}}register"
]
}
},
"response": []
}
]
},
{
"name": "Streamer_Controller",
"item": [
{
"name": "Create",
"event": [
{
"listen": "test",
"script": {
"exec": [
"var responseData = pm.response.json();\r",
"pm.environment.set(\"CreatedStreamerId\", responseData.streamerId);\r",
"\r",
"if(pm.response.status == 200){\r",
" console.log(\"StreamerController_Create_OK\");\r",
"}"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"nombre\": \"Nueva plataforma\",\r\n \"url\": \"https://NP.com\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{UrlBase}}{{UrlStreamer}}",
"host": [
"{{UrlBase}}{{UrlStreamer}}"
]
}
},
"response": []
},
{
"name": "Update",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": \"{{CreatedStreamerId}}\",\r\n \"nombre\": \"Nueva plataforma MOD\",\r\n \"url\":\"https://NPMOD.com\"\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{UrlBase}}{{UrlStreamer}}",
"host": [
"{{UrlBase}}{{UrlStreamer}}"
]
}
},
"response": []
},
{
"name": "Delete",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{UrlBase}}{{UrlStreamer}}{{CreatedStreamerId}}",
"host": [
"{{UrlBase}}{{UrlStreamer}}{{CreatedStreamerId}}"
]
}
},
"response": []
}
]
},
{
"name": "Video_Controller",
"item": [
{
"name": "GetVideoByUserName",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{UrlBase}}{{UrlVideo}}Alex",
"host": [
"{{UrlBase}}{{UrlVideo}}Alex"
]
}
},
"response": []
}
]
}
],
"description": "The `/collections` endpoints let you manage your [collections](https://learning.postman.com/docs/sending-requests/intro-to-collections/).",
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{PostLoginToken}}",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
]
}
],
"auth": {
"type": "apikey",
"apikey": [
{
"key": "key",
"value": "X-API-Key",
"type": "string"
},
{
"key": "value",
"value": "{{token}}",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "baseUrl",
"value": "https://farming-simulator.pstmn.io"
}
]
}