Skip to content

Commit 89636c3

Browse files
committed
E-mail confirmation
1 parent a575623 commit 89636c3

16 files changed

Lines changed: 952 additions & 832 deletions

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,16 @@ az containerapp create \
216216
--resource-group kairos \
217217
--environment cae-kairos \
218218
--yaml .github/capp-kairos-broker.yml
219+
```
220+
221+
# Database Migrations
222+
223+
Generate the migration:
224+
```sh
225+
dotnet ef migrations add MigrationName -p src/SpecificModule -s src/Gateway -o Infra/Migrations --context SpecificModuleContext
226+
```
227+
228+
Apply the migrations:
229+
```sh
230+
dotnet tool run dotnet-ef database update -p src/SpecificModule -s src/Gateway --context SpecificModuleContext
219231
```
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using Kairos.Account.Domain;
3+
using Kairos.Shared.Contracts;
4+
using Kairos.Shared.Contracts.Account;
5+
using MediatR;
6+
using Microsoft.AspNetCore.Identity;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace Kairos.Account.Business.UseCases;
10+
11+
internal sealed class ConfirmEmailUseCase(
12+
ILogger<ConfirmEmailUseCase> logger,
13+
UserManager<Investor> identity
14+
) : IRequestHandler<ConfirmEmailCommand, Output>
15+
{
16+
public async Task<Output> Handle(ConfirmEmailCommand input, CancellationToken cancellationToken)
17+
{
18+
var enrichers = new Dictionary<string, object?>
19+
{
20+
["CorrelationId"] = input.CorrelationId,
21+
["AccountId"] = input.AccountId,
22+
};
23+
24+
using (logger.BeginScope(enrichers))
25+
{
26+
try
27+
{
28+
return await ConfirmEmail(input);
29+
}
30+
catch (Exception ex)
31+
{
32+
logger.LogError(ex, "An unexpected error occurred");
33+
return Output.UnexpectedError([
34+
"Algum erro inesperado ocorreu... tente novamente mais tarde.",
35+
ex.Message]);
36+
}
37+
}
38+
}
39+
40+
async Task<Output> ConfirmEmail(ConfirmEmailCommand input)
41+
{
42+
if (input.AccountId is 0 || string.IsNullOrEmpty(input.ConfirmationToken))
43+
{
44+
return Output.InvalidInput(["A conta e seu token de confirmação devem ser especificados."]);
45+
}
46+
47+
var account = await identity.FindByIdAsync(input.AccountId.ToString());
48+
49+
if (account is null)
50+
{
51+
logger.LogWarning("Account not found");
52+
return Output.PolicyViolation([$"A conta {input.AccountId} não existe."]);
53+
}
54+
55+
var confirmationResult = await identity.ConfirmEmailAsync(
56+
account,
57+
input.ConfirmationToken);
58+
59+
if (confirmationResult.Succeeded is false)
60+
{
61+
var errors = confirmationResult.Errors
62+
.Select(e => e.Description)
63+
.ToList();
64+
65+
logger.LogWarning("E-mail confirmation failed. Errors: {@Errors}", errors);
66+
return Output.PolicyViolation(errors);
67+
}
68+
69+
return Output.Ok(["E-mail confirmado com sucesso!"]);
70+
}
71+
}

src/Account/Business/UseCases/OpenAccountUseCase.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public async Task<Output> Handle(
6363

6464
Investor investor = openAccountResult.Value!;
6565

66+
// TODO: usar transaction/outbox
67+
6668
var identityResult = await identity.CreateAsync(investor);
6769

6870
if (identityResult.Succeeded is false)
@@ -76,8 +78,6 @@ public async Task<Output> Handle(
7678

7779
var token = await identity.GenerateEmailConfirmationTokenAsync(investor);
7880

79-
logger.LogInformation("Account {AccountId} opened!", investor.Id);
80-
8181
await bus.Publish(
8282
new AccountOpened(
8383
investor.Id,
@@ -86,11 +86,13 @@ await bus.Publish(
8686
req.Document,
8787
req.Email,
8888
req.Birthdate,
89-
Uri.EscapeDataString(token),
89+
token,
9090
req.CorrelationId),
9191
ctx => ctx.CorrelationId = req.CorrelationId,
9292
cancellationToken);
9393

94+
logger.LogInformation("Account {AccountId} opened!", investor.Id);
95+
9496
return Output.Created([
9597
$"Conta de investimento {investor.Id} aberta!",
9698
"Confirme a abertura no e-mail que será enviado em instantes."]);

src/Account/DependencyInjection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static IServiceCollection AddIdentity(
5959
{
6060
services
6161
.AddDbContext<AccountContext>(o => o.UseSqlServer(config["Database:Broker:ConnectionString"]!))
62-
.AddIdentity<Investor, IdentityRole>(o =>
62+
.AddIdentity<Investor, IdentityRole<long>>(o =>
6363
{
6464
o.Password.RequireDigit = true;
6565
o.Password.RequiredLength = 6;

src/Account/Domain/Abstraction/KairosAccount.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
namespace Kairos.Account.Domain.Abstraction;
44

5-
public abstract class KairosAccount : IdentityUser;
5+
public abstract class KairosAccount : IdentityUser<long>;

src/Account/Infra/AccountContext.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Kairos.Account.Infra;
77

8-
internal sealed class AccountContext : IdentityDbContext<Investor, IdentityRole, string>
8+
internal sealed class AccountContext : IdentityDbContext<Investor, IdentityRole<long>, long>
99
{
1010
public AccountContext(DbContextOptions<AccountContext> options)
1111
: base(options)
@@ -19,11 +19,11 @@ protected override void OnModelCreating(ModelBuilder builder)
1919
base.OnModelCreating(builder);
2020

2121
builder.Entity<Investor>(b => b.ToTable("Account"));
22-
builder.Entity<IdentityRole>(b => b.ToTable("Role"));
23-
builder.Entity<IdentityUserRole<string>>(b => b.ToTable("AccountRole"));
24-
builder.Entity<IdentityUserClaim<string>>(b => b.ToTable("AccountClaim"));
25-
builder.Entity<IdentityUserLogin<string>>(b => b.ToTable("AccountLogin"));
26-
builder.Entity<IdentityRoleClaim<string>>(b => b.ToTable("RoleClaim"));
27-
builder.Entity<IdentityUserToken<string>>(b => b.ToTable("AccountToken"));
22+
builder.Entity<IdentityRole<long>>(b => b.ToTable("Role"));
23+
builder.Entity<IdentityUserRole<long>>(b => b.ToTable("AccountRole"));
24+
builder.Entity<IdentityUserClaim<long>>(b => b.ToTable("AccountClaim"));
25+
builder.Entity<IdentityUserLogin<long>>(b => b.ToTable("AccountLogin"));
26+
builder.Entity<IdentityRoleClaim<long>>(b => b.ToTable("RoleClaim"));
27+
builder.Entity<IdentityUserToken<long>>(b => b.ToTable("AccountToken"));
2828
}
2929
}

0 commit comments

Comments
 (0)