Skip to content

Commit 341c504

Browse files
authored
Merge pull request #17 from evans-costa/feature/soft-delete
Implement soft delete functionality and enhance email handling
2 parents bf15f0c + dc15584 commit 341c504

52 files changed

Lines changed: 2563 additions & 565 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ jobs:
1212
name: Code Formatting Check
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v6
1616

1717
- name: Setup .NET
18-
uses: actions/setup-dotnet@v4
18+
uses: actions/setup-dotnet@v5
1919
with:
2020
dotnet-version: '10.0.x'
2121
cache: true
@@ -32,25 +32,25 @@ jobs:
3232
needs: linting
3333
runs-on: ubuntu-latest
3434
steps:
35-
- uses: actions/checkout@v4
35+
- uses: actions/checkout@v6
3636
with:
3737
fetch-depth: 0
3838

3939
- name: Setup .NET
40-
uses: actions/setup-dotnet@v4
40+
uses: actions/setup-dotnet@v5
4141
with:
4242
dotnet-version: '10.0.x'
4343
cache: true
4444
cache-dependency-path: Voltiq.slnx
4545

4646
- name: Set up JDK 17
47-
uses: actions/setup-java@v4
47+
uses: actions/setup-java@v5
4848
with:
4949
java-version: 17
5050
distribution: 'zulu'
5151

5252
- name: Cache SonarCloud Scanner
53-
uses: actions/cache@v4
53+
uses: actions/cache@v5
5454
with:
5555
path: ~/.sonar/cache
5656
key: ${{ runner.os }}-sonar

src/Voltiq.API/Controllers/Auth/AuthController.cs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,38 @@
33
using Microsoft.AspNetCore.Mvc;
44
using Voltiq.Application.Features.Auth.Commands.Login;
55
using Voltiq.Application.Features.Auth.Commands.Refresh;
6-
using Voltiq.Application.Features.Users.Queries.GetCurrentUser;
6+
using Voltiq.Application.Features.Users.Commands.RegisterUser;
77
using Voltiq.Application.Mappings.Auth;
8+
using Voltiq.Application.Mappings.Users;
89

910
namespace Voltiq.API.Controllers.Auth;
1011

1112
[ApiVersion("1.0")]
12-
[Route("api/v{version:apiVersion}/auth")]
13+
[Route("api/v{version:apiVersion}")]
1314
public sealed class AuthController : BaseApiController
1415
{
16+
/// <summary>Registers a new user account.</summary>
17+
/// <response code="201">User registered successfully.</response>
18+
/// <response code="400">Validation error.</response>
19+
/// <response code="409">Email and/or document already in use.</response>
20+
[AllowAnonymous]
21+
[HttpPost("register")]
22+
[ProducesResponseType(typeof(RegisterUserResponse), StatusCodes.Status201Created)]
23+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
24+
[ProducesResponseType(StatusCodes.Status409Conflict)]
25+
public async Task<IActionResult> Register(
26+
[FromBody] RegisterUserRequest request,
27+
CancellationToken cancellationToken)
28+
{
29+
var command = request.ToCommand();
30+
var result = await Sender.Send(command, cancellationToken);
31+
32+
return result.Match(
33+
user => CreatedAtAction(nameof(Register), new { id = user.Id }, user),
34+
ToErrorResult);
35+
}
36+
37+
1538
/// <summary>Authenticates a user and returns an access token and a refresh token.</summary>
1639
/// <response code="200">Authentication successful.</response>
1740
/// <response code="400">Validation error.</response>
@@ -52,21 +75,4 @@ public async Task<IActionResult> Refresh(
5275
Ok,
5376
ToErrorResult);
5477
}
55-
56-
/// <summary>Returns the currently authenticated user.</summary>
57-
/// <response code="200">Current user data.</response>
58-
/// <response code="401">Token missing or invalid.</response>
59-
/// <response code="404">User no longer exists.</response>
60-
[HttpGet("me")]
61-
[ProducesResponseType(typeof(GetUserResponse), StatusCodes.Status200OK)]
62-
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
63-
[ProducesResponseType(StatusCodes.Status404NotFound)]
64-
public async Task<IActionResult> Me(CancellationToken cancellationToken)
65-
{
66-
var result = await Sender.Send(new GetCurrentUserQuery(), cancellationToken);
67-
68-
return result.Match(
69-
Ok,
70-
ToErrorResult);
71-
}
7278
}

src/Voltiq.API/Controllers/BaseApiController.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,15 @@ namespace Voltiq.API.Controllers;
1111
[Route("api/v{version:apiVersion}/[controller]")]
1212
public abstract class BaseApiController : ControllerBase
1313
{
14-
[field: AllowNull, MaybeNull]
14+
[field: AllowNull]
15+
[field: MaybeNull]
1516
protected ISender Sender =>
1617
field ??= HttpContext.RequestServices.GetRequiredService<ISender>();
1718

1819
protected IActionResult ToErrorResult(List<Error>? errors)
1920
{
2021
if (errors is null || errors.Count == 0)
21-
{
2222
return Problem(title: ResourceErrorMessages.TITULO_ERRO_INESPERADO, statusCode: 500);
23-
}
2423

2524
return errors.All(error => error.Type == ErrorType.Validation)
2625
? BuildValidationProblem(errors)
@@ -29,21 +28,18 @@ protected IActionResult ToErrorResult(List<Error>? errors)
2928

3029
private ObjectResult BuildProblem(Error error)
3130
{
32-
var statusCode = error.Type switch
31+
var (statusCode, title) = error.Type switch
3332
{
34-
ErrorType.Conflict => StatusCodes.Status409Conflict,
35-
ErrorType.Validation => StatusCodes.Status400BadRequest,
36-
ErrorType.NotFound => StatusCodes.Status404NotFound,
37-
ErrorType.Unauthorized => StatusCodes.Status401Unauthorized,
38-
_ => StatusCodes.Status500InternalServerError
39-
};
40-
41-
var title = error.Type switch
42-
{
43-
ErrorType.Conflict => ResourceErrorMessages.TITULO_CONFLITO,
44-
ErrorType.NotFound => ResourceErrorMessages.TITULO_NAO_ENCONTRADO,
45-
ErrorType.Unauthorized => ResourceErrorMessages.TITULO_NAO_AUTORIZADO,
46-
_ => ResourceErrorMessages.TITULO_ERRO_INESPERADO
33+
ErrorType.Conflict => (StatusCodes.Status409Conflict,
34+
ResourceErrorMessages.TITULO_CONFLITO),
35+
ErrorType.NotFound => (StatusCodes.Status404NotFound,
36+
ResourceErrorMessages.TITULO_NAO_ENCONTRADO),
37+
ErrorType.Unauthorized => (StatusCodes.Status401Unauthorized,
38+
ResourceErrorMessages.TITULO_NAO_AUTORIZADO),
39+
ErrorType.Validation => (StatusCodes.Status400BadRequest,
40+
ResourceErrorMessages.TITULO_VALIDACAO),
41+
_ => (StatusCodes.Status500InternalServerError,
42+
ResourceErrorMessages.TITULO_ERRO_INESPERADO)
4743
};
4844

4945
return Problem(
@@ -57,12 +53,12 @@ private ActionResult BuildValidationProblem(List<Error> errors)
5753
var modelStateDictionary = new ModelStateDictionary();
5854

5955
foreach (var error in errors)
60-
{
6156
modelStateDictionary.AddModelError(
6257
error.Code,
6358
error.Description);
64-
}
6559

66-
return ValidationProblem(modelStateDictionary);
60+
return ValidationProblem(
61+
title: ResourceErrorMessages.TITULO_VALIDACAO,
62+
modelStateDictionary: modelStateDictionary);
6763
}
6864
}
Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
11
using Asp.Versioning;
2-
using Microsoft.AspNetCore.Authorization;
32
using Microsoft.AspNetCore.Mvc;
4-
using Voltiq.Application.Features.Users.Commands.RegisterUser;
5-
using Voltiq.Application.Mappings.Users;
3+
using Voltiq.Application.Features.Users.Queries.GetCurrentUser;
64

75
namespace Voltiq.API.Controllers.Users;
86

97
[ApiVersion("1.0")]
10-
118
public sealed class UsersController : BaseApiController
129
{
13-
/// <summary>Registers a new user account.</summary>
14-
/// <response code="201">User registered successfully.</response>
15-
/// <response code="400">Validation error.</response>
16-
/// <response code="409">Email and/or document already in use.</response>
17-
[AllowAnonymous]
18-
[HttpPost]
19-
[ProducesResponseType(StatusCodes.Status201Created)]
20-
[ProducesResponseType(StatusCodes.Status400BadRequest)]
21-
[ProducesResponseType(StatusCodes.Status409Conflict)]
22-
public async Task<IActionResult> Register(
23-
[FromBody] RegisterUserRequest request,
24-
CancellationToken cancellationToken)
10+
/// <summary>Returns the currently authenticated user.</summary>
11+
/// <response code="200">Current user data.</response>
12+
/// <response code="401">Token missing or invalid.</response>
13+
/// <response code="404">User no longer exists.</response>
14+
[HttpGet("me")]
15+
[ProducesResponseType(typeof(GetUserResponse), StatusCodes.Status200OK)]
16+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
17+
[ProducesResponseType(StatusCodes.Status404NotFound)]
18+
public async Task<IActionResult> Me(CancellationToken cancellationToken)
2519
{
26-
var command = request.ToCommand();
27-
var result = await Sender.Send(command, cancellationToken);
20+
var result = await Sender.Send(new GetCurrentUserQuery(), cancellationToken);
2821

2922
return result.Match(
30-
user => CreatedAtAction(nameof(Register), new { id = user.Id }, user),
31-
ToErrorResult);
23+
Ok,
24+
ToErrorResult);
3225
}
3326
}

src/Voltiq.API/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
1919

2020
builder.Services.AddControllers();
21+
builder.Services.AddRouting(options => options.LowercaseUrls = true);
22+
2123
builder.Services.AddApiVersioning(options =>
2224
{
2325
options.DefaultApiVersion = new ApiVersion(1, 0);

src/Voltiq.Application/Features/Clients/ClientResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public sealed record ClientResponse(
44
Guid Id,
55
string Name,
66
string Phone,
7+
string Email,
78
string Street,
89
string Number,
910
string City,

src/Voltiq.Application/Features/Clients/Commands/RegisterClient/RegisterClientCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace Voltiq.Application.Features.Clients.Commands.RegisterClient;
66
public sealed record RegisterClientCommand(
77
string Name,
88
string Phone,
9+
string Email,
910
string Street,
1011
string Number,
1112
string City,

src/Voltiq.Application/Features/Clients/Commands/RegisterClient/RegisterClientCommandHandler.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,17 @@ public async Task<ErrorOr<ClientResponse>> Handle(RegisterClientCommand request,
2424
if (userId == Guid.Empty)
2525
return Error.Unauthorized(description: ResourceErrorMessages.TITULO_NAO_AUTORIZADO);
2626

27+
var email = Email.Create(request.Email).Value;
28+
29+
var emailExists = await clientRepository.ExistsWithEmailForUserAsync(
30+
email, userId, cancellationToken: cancellationToken);
31+
32+
if (emailExists)
33+
return Error.Conflict(description: ResourceErrorMessages.CLIENTE_EMAIL_JA_CADASTRADO);
34+
2735
var address = Address.Create(request.Street, request.Number, request.City, request.State,
2836
request.ZipCode);
29-
var client = Client.Register(userId, request.Name, request.Phone, address);
37+
var client = Client.Register(userId, request.Name, request.Phone, email, address);
3038

3139
await clientRepository.AddAsync(client, cancellationToken);
3240
await unitOfWork.SaveChangesAsync(cancellationToken);

src/Voltiq.Application/Features/Clients/Commands/RegisterClient/RegisterClientCommandValidator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public RegisterClientCommandValidator()
1313
RuleFor(x => x.Phone)
1414
.NotEmpty().WithMessage(ResourceErrorMessages.CLIENTE_TELEFONE_OBRIGATORIO);
1515

16+
RuleFor(x => x.Email)
17+
.NotEmpty().WithMessage(ResourceErrorMessages.CLIENTE_EMAIL_OBRIGATORIO)
18+
.EmailAddress().WithMessage(ResourceErrorMessages.CLIENTE_EMAIL_INVALIDO);
19+
1620
RuleFor(x => x.Street)
1721
.NotEmpty().WithMessage(ResourceErrorMessages.CLIENTE_ENDERECO_LOGRADOURO_OBRIGATORIO);
1822

src/Voltiq.Application/Features/Clients/Commands/RegisterClient/RegisterClientRequest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Voltiq.Application.Features.Clients.Commands.RegisterClient;
33
public sealed record RegisterClientRequest(
44
string Name,
55
string Phone,
6+
string Email,
67
string Street,
78
string Number,
89
string City,

0 commit comments

Comments
 (0)