diff --git a/OneBus.API/Controllers/EmployeeController.cs b/OneBus.API/Controllers/EmployeeController.cs index eaf0867..af85c9a 100644 --- a/OneBus.API/Controllers/EmployeeController.cs +++ b/OneBus.API/Controllers/EmployeeController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/employees")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/EmployeeWorkdayController.cs b/OneBus.API/Controllers/EmployeeWorkdayController.cs index 935566f..7b53a05 100644 --- a/OneBus.API/Controllers/EmployeeWorkdayController.cs +++ b/OneBus.API/Controllers/EmployeeWorkdayController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/employeesWorkdays")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/LineController.cs b/OneBus.API/Controllers/LineController.cs index c381ccd..23bbf12 100644 --- a/OneBus.API/Controllers/LineController.cs +++ b/OneBus.API/Controllers/LineController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/lines")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/LineTimeController.cs b/OneBus.API/Controllers/LineTimeController.cs index c0a9ccf..86227f3 100644 --- a/OneBus.API/Controllers/LineTimeController.cs +++ b/OneBus.API/Controllers/LineTimeController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/linesTimes")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/MaintenanceController.cs b/OneBus.API/Controllers/MaintenanceController.cs index c696948..a399008 100644 --- a/OneBus.API/Controllers/MaintenanceController.cs +++ b/OneBus.API/Controllers/MaintenanceController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/maintenances")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/UserController.cs b/OneBus.API/Controllers/UserController.cs index 00aad25..cbbb7f0 100644 --- a/OneBus.API/Controllers/UserController.cs +++ b/OneBus.API/Controllers/UserController.cs @@ -1,7 +1,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using OneBus.API.Extensions; using OneBus.Application.DTOs.Login; using OneBus.Application.Interfaces.Services; +using OneBus.Domain.Commons.Result; +using OneBus.Domain.Models; using Swashbuckle.AspNetCore.Annotations; namespace OneBus.API.Controllers @@ -24,16 +27,25 @@ public UserController(IUserService userService) /// Efetuar login /// /// - /// POST de Login + /// Example: + /// + /// POST /logins + /// { + /// "email": "helloworld@gmail.com", + /// "password": "dasdfsf35346353tsd" + /// } /// /// /// /// Usuário autenticado [AllowAnonymous] [HttpPost("logins")] - public Task LoginAsync([FromBody] LoginDTO login, CancellationToken cancellationToken = default) + [ProducesResponseType(typeof(SuccessResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(InvalidResult), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(typeof(NotFoundResult), StatusCodes.Status404NotFound)] + public async Task LoginAsync([FromBody] LoginDTO login, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + return (await _userService.LoginAsync(login, cancellationToken)).ToActionResult(); } } } diff --git a/OneBus.API/Controllers/VehicleController.cs b/OneBus.API/Controllers/VehicleController.cs index eb6b78f..d914b3a 100644 --- a/OneBus.API/Controllers/VehicleController.cs +++ b/OneBus.API/Controllers/VehicleController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/vehicles")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Controllers/VehicleOperationController.cs b/OneBus.API/Controllers/VehicleOperationController.cs index 10c6ffb..91bcf79 100644 --- a/OneBus.API/Controllers/VehicleOperationController.cs +++ b/OneBus.API/Controllers/VehicleOperationController.cs @@ -10,6 +10,7 @@ namespace OneBus.API.Controllers { + [NonController] [Route("api/v1/vehiclesOperations")] [ApiController] [Produces("application/json")] diff --git a/OneBus.API/Extensions/ResultExtensions.cs b/OneBus.API/Extensions/ResultExtensions.cs index e4aac99..9e8923b 100644 --- a/OneBus.API/Extensions/ResultExtensions.cs +++ b/OneBus.API/Extensions/ResultExtensions.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Http.HttpResults; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using OneBus.Domain.Commons.Result; namespace OneBus.API.Extensions @@ -12,7 +11,7 @@ public static IActionResult ToActionResult(this Result result) { SuccessResult => new OkObjectResult(result), ConflictResult => new ConflictObjectResult(result), - NotFound => new NotFoundObjectResult(result), + NotFoundResult => new NotFoundObjectResult(result), ErrorResult => new BadRequestObjectResult(result), InvalidResult => new UnprocessableEntityObjectResult(result), _ => new StatusCodeResult(StatusCodes.Status500InternalServerError) diff --git a/OneBus.Application/Interfaces/Services/IUserService.cs b/OneBus.Application/Interfaces/Services/IUserService.cs index 8279fed..3c1baa4 100644 --- a/OneBus.Application/Interfaces/Services/IUserService.cs +++ b/OneBus.Application/Interfaces/Services/IUserService.cs @@ -1,11 +1,17 @@ -using OneBus.Application.DTOs.User; +using OneBus.Application.DTOs.Login; +using OneBus.Application.DTOs.User; +using OneBus.Domain.Commons.Result; using OneBus.Domain.Entities; using OneBus.Domain.Filters; +using OneBus.Domain.Models; namespace OneBus.Application.Interfaces.Services { public interface IUserService : IBaseService { + public Task> LoginAsync( + LoginDTO loginDTO, + CancellationToken cancellationToken = default); } } diff --git a/OneBus.Application/Services/TokenService.cs b/OneBus.Application/Services/TokenService.cs index e3bd47f..312a1ee 100644 --- a/OneBus.Application/Services/TokenService.cs +++ b/OneBus.Application/Services/TokenService.cs @@ -34,11 +34,26 @@ public TokenModel Generate(ReadUserDTO user) DateTime createdAt = DateTime.UtcNow; DateTime expiresAt = createdAt.AddDays(_tokenSettings.DaysUntilExpires); - return new TokenModel(Create( + string token = Create( key: Encoding.ASCII.GetBytes(_tokenSettings.Key), identity, createdAt, - expiresAt) + expiresAt); + + string refreshToken = Create( + key: Encoding.ASCII.GetBytes(_tokenSettings.Key), + identity, + createdAt, + createdAt.AddDays(_tokenSettings.DaysUntilExpires * 2)); + + return new TokenModel( + user.Id!.Value, + user.Name, + user.Email, + token, + createdAt, + expiresAt, + refreshToken ); } diff --git a/OneBus.Application/Services/UserService.cs b/OneBus.Application/Services/UserService.cs index a173b4e..412dd53 100644 --- a/OneBus.Application/Services/UserService.cs +++ b/OneBus.Application/Services/UserService.cs @@ -1,21 +1,52 @@ using FluentValidation; +using Mapster; +using OneBus.Application.DTOs.Login; using OneBus.Application.DTOs.User; +using OneBus.Application.Extensions; using OneBus.Application.Interfaces.Services; +using OneBus.Domain.Commons; +using OneBus.Domain.Commons.Result; using OneBus.Domain.Entities; using OneBus.Domain.Filters; using OneBus.Domain.Interfaces.Repositories; +using OneBus.Domain.Models; namespace OneBus.Application.Services { public class UserService : BaseService, IUserService { + private readonly ITokenService _tokenService; + public UserService( - IBaseRepository baseRepository, - IValidator createValidator, - IValidator updateValidator) - : base(baseRepository, createValidator, updateValidator) + IUserRepository userRepository, + ITokenService tokenService, + IValidator createValidator, + IValidator updateValidator) + : base(userRepository, createValidator, updateValidator) { + _tokenService = tokenService; + } + + public async Task> LoginAsync( + LoginDTO loginDTO, CancellationToken cancellationToken = default) + { + if (loginDTO is null) + return ErrorResult.Create(ErrorUtils.InvalidParameter("Login")); + + var user = await _baseReadOnlyRepository.GetOneAsync(c => c.Email.ToLower() == loginDTO.Email.ToLower(), + cancellationToken: cancellationToken); + + if (user is null) + return NotFoundResult.Create(ErrorUtils.EntityNotFound(nameof(loginDTO.Email))); + + if (!user.Password.Verify(loginDTO.Password, user.Salt)) + return InvalidResult.Create(ErrorUtils.InvalidParameter("Senha")); + + var readUserDTO = user.Adapt(); + + var tokenModel = _tokenService.Generate(readUserDTO); + return SuccessResult.Create(tokenModel); } protected override void UpdateFields(User entity, UpdateUserDTO updateDTO) diff --git a/OneBus.Domain/Commons/ErrorUtils.cs b/OneBus.Domain/Commons/ErrorUtils.cs index 0b657c7..33f44fa 100644 --- a/OneBus.Domain/Commons/ErrorUtils.cs +++ b/OneBus.Domain/Commons/ErrorUtils.cs @@ -6,9 +6,18 @@ public static class ErrorUtils /// Registro não encontrado. /// /// - public static Error EntityNotFound() + public static Error EntityNotFound(string field = "id") { - return new Error("Registro não encontrado.", "id"); + return new Error("Registro não encontrado.", field); + } + + /// + /// Valor inválido. + /// + /// + public static Error InvalidParameter(string field = "id") + { + return new Error("Valor inválido.", field); } /// diff --git a/OneBus.Domain/Interfaces/Repositories/IBaseReadOnlyRepository.cs b/OneBus.Domain/Interfaces/Repositories/IBaseReadOnlyRepository.cs index f6f9216..09df04e 100644 --- a/OneBus.Domain/Interfaces/Repositories/IBaseReadOnlyRepository.cs +++ b/OneBus.Domain/Interfaces/Repositories/IBaseReadOnlyRepository.cs @@ -1,6 +1,7 @@ using OneBus.Domain.Commons; using OneBus.Domain.Entities; using OneBus.Domain.Filters; +using System.Linq.Expressions; namespace OneBus.Domain.Interfaces.Repositories { @@ -9,23 +10,38 @@ public interface IBaseReadOnlyRepository where TFilter : BaseFilter { Task GetOneAsync( - Predicate predicate, - DbQueryOptions? dbQueryOptions = null, + Expression> expression, + DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default); Task> GetManyAsync( - Predicate predicate, - DbQueryOptions? dbQueryOptions = null, + Expression> expression, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default); + + Task> GetManyAsync( + TFilter filter, + DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default); Task> GetPaginedAsync( TFilter filter, - DbQueryOptions? dbQueryOptions = null, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default); + + Task LongCountAsync( + TFilter filter, + DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default); Task LongCountAsync( - TFilter filter, - DbQueryOptions? dbQueryOptions = null, + Expression> expression, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default); + + Task AnyAsync( + Expression> expression, + DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default); } } diff --git a/OneBus.Domain/Models/TokenModel.cs b/OneBus.Domain/Models/TokenModel.cs index 559513a..5de8c40 100644 --- a/OneBus.Domain/Models/TokenModel.cs +++ b/OneBus.Domain/Models/TokenModel.cs @@ -1,12 +1,50 @@ -namespace OneBus.Domain.Models +using System.Reflection; + +namespace OneBus.Domain.Models { public class TokenModel { - public TokenModel(string token) + public TokenModel( + long userId, + string name, + string email, + string token, + DateTime created, + DateTime expiration, + string refreshToken) { + Name = name; + Email = email; Token = token; + UserId = userId; + Created = created; + Expiration = expiration; + RefreshToken = refreshToken; } + public long UserId { get; } + + public string Email { get; } + + public string Name { get; } + public string Token { get; } + + public string RefreshToken { get; } + + public string Version + { + get + { + var version = Assembly.GetExecutingAssembly().GetName().Version; + return $"{version?.Major}.{version?.Minor}.{version?.Build}"; + } + } + + public DateTime Created { get; } + + public DateTime Expiration { get; } + + public double DurationInSeconds { get { return (Expiration - Created).TotalSeconds; } } } } diff --git a/OneBus.Infra.Data/Repositories/BaseReadOnlyRepository.cs b/OneBus.Infra.Data/Repositories/BaseReadOnlyRepository.cs index f049333..aceb23b 100644 --- a/OneBus.Infra.Data/Repositories/BaseReadOnlyRepository.cs +++ b/OneBus.Infra.Data/Repositories/BaseReadOnlyRepository.cs @@ -6,6 +6,7 @@ using OneBus.Infra.Data.DbContexts; using OneBus.Infra.Data.Extensions; using System.Linq.Dynamic.Core; +using System.Linq.Expressions; namespace OneBus.Infra.Data.Repositories { @@ -23,26 +24,40 @@ protected BaseReadOnlyRepository(OneBusDbContext dbContext) } public virtual async Task> GetManyAsync( - Predicate predicate, + Expression> expression, DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default) { IQueryable query = ApplyDbQueryOptions(dbQueryOptions); return await query - .Where(c => predicate(c)) + .Where(expression) + .ToListAsync(cancellationToken); + } + + public virtual async Task> GetManyAsync( + TFilter filter, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default) + { + Expression> expression = ApplyFilter(filter); + + IQueryable query = ApplyDbQueryOptions(dbQueryOptions); + + return await query + .Where(expression) .ToListAsync(cancellationToken); } public virtual async Task GetOneAsync( - Predicate predicate, + Expression> expression, DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default) { IQueryable query = ApplyDbQueryOptions(dbQueryOptions); return await query - .Where(c => predicate(c)) + .Where(expression) .FirstOrDefaultAsync(cancellationToken); } @@ -51,32 +66,54 @@ public virtual async Task> GetPaginedAsync( DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default) { - Predicate predicate = ApplyFilter(filter); + Expression> expression = ApplyFilter(filter); IQueryable query = ApplyDbQueryOptions(dbQueryOptions); return await query - .Where(c => predicate(c)) - .OrderBy("{0} {1}", filter.OrderField, filter.OrderType) + .Where(expression) + .OrderBy($"{filter.OrderField} {filter.OrderType}") .Skip((int)((filter.CurrentPage - 1) * filter.PageSize)) .Take((int)filter.PageSize) .ToListAsync(cancellationToken); } + public virtual async Task LongCountAsync( + Expression> expression, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default) + { + IQueryable query = ApplyDbQueryOptions(dbQueryOptions); + + return await query + .Where(expression) + .LongCountAsync(cancellationToken); + } + public virtual async Task LongCountAsync( TFilter filter, DbQueryOptions? dbQueryOptions = null, CancellationToken cancellationToken = default) { - Predicate predicate = ApplyFilter(filter); + Expression> expression = ApplyFilter(filter); IQueryable query = ApplyDbQueryOptions(dbQueryOptions); return await query - .Where(c => predicate(c)) + .Where(expression) .LongCountAsync(cancellationToken); } + public virtual async Task AnyAsync( + Expression> expression, + DbQueryOptions? dbQueryOptions = null, + CancellationToken cancellationToken = default) + { + IQueryable query = ApplyDbQueryOptions(dbQueryOptions); + + return await query.AnyAsync(expression, cancellationToken); + } + protected virtual IQueryable ApplyDbQueryOptions(DbQueryOptions? dbQueryOptions) { IQueryable query = _dbSet; @@ -92,9 +129,9 @@ protected virtual IQueryable ApplyDbQueryOptions(DbQueryOptions? dbQuer return query.IgnoreQueryFilters(); } - protected virtual Predicate ApplyFilter(TFilter filter) + protected virtual Expression> ApplyFilter(TFilter filter) { - return c => true; + return _ => true; } } } diff --git a/OneBus.Infra.Ioc/DependencyInjection.cs b/OneBus.Infra.Ioc/DependencyInjection.cs index ee9f497..23b2327 100644 --- a/OneBus.Infra.Ioc/DependencyInjection.cs +++ b/OneBus.Infra.Ioc/DependencyInjection.cs @@ -1,6 +1,7 @@ using FluentValidation; using Microsoft.Extensions.DependencyInjection; using OneBus.Application.Interfaces.Services; +using OneBus.Application.Services; using OneBus.Domain.Interfaces.Repositories; using OneBus.Infra.Data.Repositories; @@ -24,6 +25,7 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi .AsImplementedInterfaces() .WithScopedLifetime()); + services.AddScoped(); return services; } }