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