diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index de173fa..7b639eb 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -194,6 +194,9 @@ jobs:
environment: dev
permissions:
contents: read
+ outputs:
+ CONFIG_VERSION: ${{ steps.plan-upload.outputs.configuration_version_id }}
+
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -215,8 +218,9 @@ jobs:
db_user = "${{ secrets.BMB_MYSQL_USER }}"
db_pwd = "${{ secrets.BMB_MYSQL_PASSWORD }}"
rds_cluster_identifier = "${{ vars.BMB_MYSQL_CLUSTER }}"
- access_key_id = "${{ secrets.AWS_ACCESS_KEY_ID }}"
- secret_access_key = "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
+ api_access_key_id = "${{ secrets.AWS_API_ACCESS_KEY_ID }}"
+ api_secret_access_key = "${{ secrets.AWS_API_SECRET_ACCESS_KEY }}"
+ user_pool_name = "${{ vars.BMB_USER_POOL_NAME }}"
EOF
- name: Upload Configuration
@@ -239,4 +243,18 @@ jobs:
id: apply
with:
run: ${{ steps.apply-run.outputs.run_id }}
- comment: "Confirmed from GitHub Actions CI ${{ github.sha }}"
\ No newline at end of file
+ comment: "Confirmed from GitHub Actions CI ${{ github.sha }}"
+
+ destroy-plan:
+ name: "Create terraform destroy plan"
+ needs: [create-app]
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Create plan
+ uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.1
+ id: destroy-plan
+ with:
+ workspace: ${{ env.TF_WORKSPACE }}
+ configuration_version: ${{ NEEDS.create-app.outputs.CONFIG_VERSION }}
+ is_destroy: true
\ No newline at end of file
diff --git a/.github/workflows/terraform-plan.yaml b/.github/workflows/terraform-plan.yaml
index ad65408..73e7fde 100644
--- a/.github/workflows/terraform-plan.yaml
+++ b/.github/workflows/terraform-plan.yaml
@@ -36,8 +36,9 @@ jobs:
db_user = "${{ secrets.BMB_MYSQL_USER }}"
db_pwd = "${{ secrets.BMB_MYSQL_PASSWORD }}"
rds_cluster_identifier = "${{ vars.BMB_MYSQL_CLUSTER }}"
- access_key_id = "${{ secrets.AWS_ACCESS_KEY_ID }}"
- secret_access_key = "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
+ api_access_key_id = "${{ secrets.AWS_API_ACCESS_KEY_ID }}"
+ api_secret_access_key = "${{ secrets.AWS_API_SECRET_ACCESS_KEY }}"
+ user_pool_name = "${{ vars.BMB_USER_POOL_NAME }}"
EOF
- name: Upload Configuration
diff --git a/FIAP.TechChallenge-P3.png b/FIAP.TechChallenge-P3.png
new file mode 100644
index 0000000..20f736d
Binary files /dev/null and b/FIAP.TechChallenge-P3.png differ
diff --git a/FIAP.TechChallenge.ByteMeBurger.sln b/FIAP.TechChallenge.ByteMeBurger.sln
index b496a28..7b2ff80 100644
--- a/FIAP.TechChallenge.ByteMeBurger.sln
+++ b/FIAP.TechChallenge.ByteMeBurger.sln
@@ -89,6 +89,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "terraform", "terraform", "{
tf\variables.tf = tf\variables.tf
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway", "src\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.csproj", "{36FD200F-16AB-44A6-A103-E5EABD6EA263}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test", "tests\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test\FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test.csproj", "{46B22CD7-BB91-4E80-870F-28DA476ED6A3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -118,6 +122,8 @@ Global
{9E8287AF-C7A6-46A6-96D8-8460642ADE4B} = {5EC029C6-230A-4782-9B62-CB475C7D05F8}
{93545102-0476-48CB-8074-5C71AC81E040} = {C244E1D6-E1EB-4314-9F39-BB59FA1F7C71}
{B2898AA3-86A6-4AF1-B27F-A3706B2B0557} = {2F192BA8-59FC-4B0C-B59D-511EFA89F428}
+ {36FD200F-16AB-44A6-A103-E5EABD6EA263} = {5EC029C6-230A-4782-9B62-CB475C7D05F8}
+ {46B22CD7-BB91-4E80-870F-28DA476ED6A3} = {C244E1D6-E1EB-4314-9F39-BB59FA1F7C71}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B8F244B5-F703-441C-8A8A-C720F605709C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -188,5 +194,13 @@ Global
{93545102-0476-48CB-8074-5C71AC81E040}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93545102-0476-48CB-8074-5C71AC81E040}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93545102-0476-48CB-8074-5C71AC81E040}.Release|Any CPU.Build.0 = Release|Any CPU
+ {36FD200F-16AB-44A6-A103-E5EABD6EA263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36FD200F-16AB-44A6-A103-E5EABD6EA263}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36FD200F-16AB-44A6-A103-E5EABD6EA263}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36FD200F-16AB-44A6-A103-E5EABD6EA263}.Release|Any CPU.Build.0 = Release|Any CPU
+ {46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46B22CD7-BB91-4E80-870F-28DA476ED6A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/Auth/BmbRoles.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/Auth/BmbRoles.cs
new file mode 100644
index 0000000..b26e8c0
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/Auth/BmbRoles.cs
@@ -0,0 +1,8 @@
+namespace FIAP.TechChallenge.ByteMeBurger.Api.Auth;
+
+public static class BmbRoles
+{
+ public const string Admin = "admin";
+ public const string Kitchen = "kitchen";
+ public const string Customer = "customer";
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/CustomersController.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/CustomersController.cs
index 0037b08..0ef16c4 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/CustomersController.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/CustomersController.cs
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
+using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Customers;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
@@ -12,7 +13,6 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
///
/// Customer service (port implementation).
/// Logger
-[Obsolete("To be migrated to different project")]
[Route("api/[controller]")]
[Produces("application/json")]
[Consumes("application/json")]
@@ -28,7 +28,7 @@ public class CustomersController(ICustomerService customerService, ILoggerCancellation token
/// Customer
[HttpGet]
- [Authorize(Roles = "admin")]
+ [Authorize(Roles = BmbRoles.Admin)]
public async Task> Get([FromQuery] [MaxLength(14)] string cpf,
CancellationToken cancellationToken)
{
@@ -51,6 +51,7 @@ public async Task> Get([FromQuery] [MaxLength(14)] str
/// Cancellation token
/// Customer
[HttpPost]
+ [Authorize(Roles = BmbRoles.Customer)]
public async Task> Post([FromBody] CreateCustomerRequest createCustomerRequest,
CancellationToken cancellationToken)
{
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/OrdersController.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/OrdersController.cs
index 3130cbf..cd1e84f 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/OrdersController.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/OrdersController.cs
@@ -52,6 +52,7 @@ public async Task> Post(
/// Cancellation token
/// Orders list
[HttpGet]
+ [Authorize(Roles = $"{BmbRoles.Admin},{BmbRoles.Kitchen}")]
public async Task>> Get(bool listAll,
CancellationToken cancellationToken)
{
@@ -71,6 +72,7 @@ public async Task>> Get(bool l
/// Cancellation token.
/// Order details
[HttpGet("{id:guid}")]
+ [Authorize(Roles = $"{BmbRoles.Admin},{BmbRoles.Kitchen}")]
public async Task> Get(Guid id, CancellationToken cancellationToken)
{
logger.LogInformation("Getting order with ID: {OrderId}", id);
@@ -98,6 +100,7 @@ public async Task> Get(Guid id, CancellationToken c
/// Cancellation token
[Route("{id:guid}/status")]
[HttpPatch]
+ [Authorize(Roles = $"{BmbRoles.Admin}")]
public async Task> Patch(Guid id,
[FromBody] UpdateOrderStatusRequest command,
CancellationToken cancellationToken)
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/PaymentsController.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/PaymentsController.cs
index 6b595cd..260a98e 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/PaymentsController.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/PaymentsController.cs
@@ -1,3 +1,4 @@
+using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Payment;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
@@ -14,7 +15,7 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
[ApiConventionType(typeof(DefaultApiConventions))]
[Produces("application/json")]
[Consumes("application/json")]
-[Authorize]
+[Authorize(Roles = BmbRoles.Admin)]
public class PaymentsController : ControllerBase
{
private readonly IPaymentService _paymentService;
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/ProductsController.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/ProductsController.cs
index d395724..be54f31 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/ProductsController.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/Controllers/ProductsController.cs
@@ -1,3 +1,4 @@
+using FIAP.TechChallenge.ByteMeBurger.Api.Auth;
using FIAP.TechChallenge.ByteMeBurger.Api.Model.Products;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Contracts;
using FIAP.TechChallenge.ByteMeBurger.Controllers.Dto;
@@ -17,7 +18,7 @@ namespace FIAP.TechChallenge.ByteMeBurger.Api.Controllers;
[ApiConventionType(typeof(DefaultApiConventions))]
[Produces("application/json")]
[Consumes("application/json")]
-[Authorize]
+[Authorize(Roles = BmbRoles.Admin)]
public class ProductsController(IProductService productService, ILogger logger)
: ControllerBase
{
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/DomainEventsHandler.cs b/src/FIAP.TechChallenge.ByteMeBurger.Api/DomainEventsHandler.cs
index 25cb56f..9d1913f 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/DomainEventsHandler.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/DomainEventsHandler.cs
@@ -40,7 +40,7 @@ public DomainEventsHandler(ILogger logger, HybridCache cach
private void OnCustomerRegistered(object? sender, CustomerRegistered e)
{
_logger.LogInformation("New Customer registered: {@Customer}", e.Payload);
- _logger.LogInformation("Sending email to customer: {CustomerName}", e.Payload.Name);
+ _publisher.PublishAsync(e).ConfigureAwait(false);
}
private void OnOrderStatusChanged(object? sender, OrderStatusChanged e)
@@ -78,6 +78,7 @@ private void OnProductDeleted(object? sender, ProductDeleted e)
private void OnProductCreated(object? sender, ProductCreated e)
{
_logger.LogInformation("Product created: {@Product}", e.Payload);
+ _publisher.PublishAsync(e).ConfigureAwait(false);
}
private void OnPaymentCreated(object? sender, PaymentCreated e)
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Api/appsettings.Development.json b/src/FIAP.TechChallenge.ByteMeBurger.Api/appsettings.Development.json
index e726935..8e77f07 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Api/appsettings.Development.json
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Api/appsettings.Development.json
@@ -32,6 +32,14 @@
"ClientSecret": "",
"ClientId": ""
},
+ "CognitoSettings": {
+ "UserPoolId": "",
+ "UserPoolClientId": "",
+ "Enabled": true,
+ "Region": "us-east-1",
+ "ClientSecret": "",
+ "ClientId": ""
+ },
"JwtOptions": {
"Issuer": "https://localhost:7000",
"Audience": "https://localhost:7000",
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoClientFactory.cs b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoClientFactory.cs
new file mode 100644
index 0000000..b7ec35d
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoClientFactory.cs
@@ -0,0 +1,17 @@
+using Amazon;
+using Amazon.CognitoIdentityProvider;
+using Amazon.Runtime;
+using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
+using Microsoft.Extensions.Options;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;
+
+public class CognitoClientFactory(IOptions settings) : ICognitoClientFactory
+{
+ public IAmazonCognitoIdentityProvider CreateClient()
+ {
+ return new AmazonCognitoIdentityProviderClient(
+ new BasicAWSCredentials(settings.Value.ClientId, settings.Value.ClientSecret),
+ RegionEndpoint.GetBySystemName(settings.Value.Region));
+ }
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoSettings.cs b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoSettings.cs
new file mode 100644
index 0000000..c33c08e
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoSettings.cs
@@ -0,0 +1,38 @@
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;
+
+///
+/// Cognito User Pool settings
+///
+public class CognitoSettings
+{
+ ///
+ /// User Pool Id
+ ///
+ public string UserPoolId { get; set; } = string.Empty;
+
+
+ ///
+ /// Client Id
+ ///
+ public string UserPoolClientId { get; set; } = string.Empty;
+
+ ///
+ /// Enabled
+ ///
+ public bool Enabled { get; set; } = false;
+
+ ///
+ /// AWS Region
+ ///
+ public string Region { get; set; } = string.Empty;
+
+ ///
+ /// AWS Secret Id
+ ///
+ public string ClientSecret { get; set; } = string.Empty;
+
+ ///
+ /// AWS Client Id
+ ///
+ public string ClientId { get; set; } = string.Empty;
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoUserManager.cs b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoUserManager.cs
new file mode 100644
index 0000000..cf96717
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/CognitoUserManager.cs
@@ -0,0 +1,122 @@
+using System.Security.Cryptography;
+using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
+using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
+using Amazon.CognitoIdentityProvider;
+using Amazon.CognitoIdentityProvider.Model;
+using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
+using FIAP.TechChallenge.ByteMeBurger.Domain.Base;
+using Microsoft.Extensions.Options;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;
+
+public class CognitoUserManager : ICustomerRepository
+{
+ private readonly IAmazonCognitoIdentityProvider _cognitoClient;
+ private readonly string _userPoolId;
+ private readonly string _clientId;
+
+ public CognitoUserManager(ICognitoClientFactory cognitoClientFactory, IOptions settings)
+ {
+ _cognitoClient = cognitoClientFactory.CreateClient();
+ _userPoolId = settings.Value.UserPoolId;
+ _clientId = settings.Value.UserPoolClientId;
+ }
+
+ public async Task FindByCpfAsync(string cpf)
+ {
+ try
+ {
+ var response = await _cognitoClient.AdminGetUserAsync(new AdminGetUserRequest
+ {
+ UserPoolId = _userPoolId,
+ Username = cpf
+ });
+
+ var email = response.UserAttributes.First(attr => attr.Name == "email").Value;
+ var name = response.UserAttributes.First(attr => attr.Name == "name").Value;
+ var sub = response.UserAttributes.First(attr => attr.Name == "sub").Value;
+ var customer = new Customer(Guid.Parse(sub), cpf, name, email);
+
+ return customer;
+ }
+ catch (UserNotFoundException)
+ {
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error fetching user: {ex.Message}");
+ throw;
+ }
+ }
+
+ public async Task CreateAsync(Customer customer)
+ {
+ try
+ {
+ var signUpResponse = await _cognitoClient.AdminCreateUserAsync(new AdminCreateUserRequest()
+ {
+ Username = customer.Cpf,
+ UserPoolId = _userPoolId,
+ UserAttributes =
+ {
+ new AttributeType { Name = "email", Value = customer.Email },
+ new AttributeType { Name = "name", Value = customer.Name }
+ }
+ });
+
+ customer.Id = Guid.Parse(signUpResponse.User.Attributes.First(a=>a.Name is "sub").Value);
+ return customer;
+ }
+ catch (UsernameExistsException ex)
+ {
+ Console.WriteLine($"Error registering user: {ex.Message}");
+ throw new DomainException("There's already a customer using the provided CPF value.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error registering user: {ex.Message}");
+ throw;
+ }
+ }
+
+ private static string GenerateRandomPassword(int length)
+ {
+ using var rng = RandomNumberGenerator.Create();
+ var characterSets = new[]
+ {
+ "abcdefghijklmnopqrstuvwxyz",
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ "1234567890",
+ "!@#$%^&*()"
+ };
+ var allChars = string.Concat(characterSets);
+ var passwordChars = new char[length];
+ // Ensure the password contains at least one character from each character set
+ for (int i = 0; i < characterSets.Length && i < length; i++)
+ {
+ passwordChars[i] = GetRandomChar(characterSets[i], rng);
+ }
+ // Fill the rest of the password with random characters
+ for (int i = characterSets.Length; i < length; i++)
+ {
+ passwordChars[i] = GetRandomChar(allChars, rng);
+ }
+ // Shuffle the password to ensure randomness
+ passwordChars = passwordChars.OrderBy(_ => GetRandomInt(rng, int.MaxValue)).ToArray();
+ return new string(passwordChars);
+ }
+ private static char GetRandomChar(string chars, RandomNumberGenerator rng)
+ {
+ var bytes = new byte[4];
+ rng.GetBytes(bytes);
+ var index = BitConverter.ToUInt32(bytes, 0) % chars.Length;
+ return chars[(int)index];
+ }
+ private static int GetRandomInt(RandomNumberGenerator rng, int max)
+ {
+ var bytes = new byte[4];
+ rng.GetBytes(bytes);
+ return (int)(BitConverter.ToUInt32(bytes, 0) % max);
+ }
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.csproj b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.csproj
new file mode 100644
index 0000000..6c97a87
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/Factory/ICognitoClientFactory.cs b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/Factory/ICognitoClientFactory.cs
new file mode 100644
index 0000000..50a51b0
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/Factory/ICognitoClientFactory.cs
@@ -0,0 +1,8 @@
+using Amazon.CognitoIdentityProvider;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
+
+public interface ICognitoClientFactory
+{
+ IAmazonCognitoIdentityProvider CreateClient();
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/ServiceExtensions.cs b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/ServiceExtensions.cs
new file mode 100644
index 0000000..c275525
--- /dev/null
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway/ServiceExtensions.cs
@@ -0,0 +1,30 @@
+using System.Diagnostics.CodeAnalysis;
+using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
+using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;
+
+[ExcludeFromCodeCoverage]
+public class CognitoSettingsSetup(IConfiguration configuration) : IConfigureOptions
+{
+ public void Configure(CognitoSettings options)
+ {
+ configuration
+ .GetSection(nameof(CognitoSettings))
+ .Bind(options);
+ }
+}
+
+[ExcludeFromCodeCoverage]
+public static class ServiceExtensions
+{
+ public static void ConfigureCognito(this IServiceCollection services)
+ {
+ services.ConfigureOptions();
+ services.AddSingleton();
+ services.AddScoped();
+ }
+}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.DI/FIAP.TechChallenge.ByteMeBurger.DI.csproj b/src/FIAP.TechChallenge.ByteMeBurger.DI/FIAP.TechChallenge.ByteMeBurger.DI.csproj
index 246e253..24447ab 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.DI/FIAP.TechChallenge.ByteMeBurger.DI.csproj
+++ b/src/FIAP.TechChallenge.ByteMeBurger.DI/FIAP.TechChallenge.ByteMeBurger.DI.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.DI/ServiceCollectionsExtensions.cs b/src/FIAP.TechChallenge.ByteMeBurger.DI/ServiceCollectionsExtensions.cs
index 8ea190c..58aacaa 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.DI/ServiceCollectionsExtensions.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.DI/ServiceCollectionsExtensions.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using FIAP.TechChallenge.ByteMeBurger.Application;
+using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway;
using FIAP.TechChallenge.ByteMeBurger.Controllers;
using FIAP.TechChallenge.ByteMeBurger.Domain.Interfaces;
using FIAP.TechChallenge.ByteMeBurger.FakePayment.Gateway;
@@ -17,6 +18,7 @@ public static class ServiceCollectionsExtensions
{
public static void IoCSetup(this IServiceCollection serviceCollection, IConfiguration configuration)
{
+ serviceCollection.ConfigureCognito();
serviceCollection.ConfigurePersistenceApp(configuration);
ConfigurePaymentGateway(serviceCollection);
ConfigHybridCache(serviceCollection, configuration);
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Persistence/ServiceExtensions.cs b/src/FIAP.TechChallenge.ByteMeBurger.Persistence/ServiceExtensions.cs
index c0cd5f6..8aa53db 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Persistence/ServiceExtensions.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Persistence/ServiceExtensions.cs
@@ -16,10 +16,9 @@ public static void ConfigurePersistenceApp(this IServiceCollection services, ICo
{
if (string.IsNullOrWhiteSpace(configuration.GetConnectionString("MySql")))
{
- services.AddScoped()
- .AddScoped(_ => new InMemoryCustomerRepository([]))
- .AddScoped(_ => new InMemoryProductRepository([]))
- .AddScoped();
+ services.AddSingleton()
+ .AddSingleton(_ => new InMemoryProductRepository([]))
+ .AddSingleton();
}
else
{
@@ -34,7 +33,6 @@ public static void ConfigurePersistenceApp(this IServiceCollection services, ICo
});
services.AddScoped()
- .AddScoped()
.AddScoped()
.AddScoped();
}
diff --git a/src/FIAP.TechChallenge.ByteMeBurger.Publisher.Sqs/SqsService.cs b/src/FIAP.TechChallenge.ByteMeBurger.Publisher.Sqs/SqsService.cs
index fc0e226..4773844 100644
--- a/src/FIAP.TechChallenge.ByteMeBurger.Publisher.Sqs/SqsService.cs
+++ b/src/FIAP.TechChallenge.ByteMeBurger.Publisher.Sqs/SqsService.cs
@@ -19,6 +19,16 @@ public async Task PublishAsync(DomainEvent @event)
var queueUrl = await client.GetQueueUrlAsync(sqsSettingsOptions.Value.QueueName);
var request = new SendMessageRequest
{
+ MessageAttributes = new Dictionary
+ {
+ {
+ "EventType", new MessageAttributeValue
+ {
+ DataType = "String",
+ StringValue = @event.GetType().Name
+ }
+ }
+ },
MessageBody = JsonSerializer.Serialize(@event.Payload),
QueueUrl = queueUrl.QueueUrl
};
diff --git a/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoClientFactoryTests.cs b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoClientFactoryTests.cs
new file mode 100644
index 0000000..b123b62
--- /dev/null
+++ b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoClientFactoryTests.cs
@@ -0,0 +1,32 @@
+using Amazon.CognitoIdentityProvider;
+using FluentAssertions;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
+using Moq;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test;
+
+[TestSubject(typeof(CognitoClientFactory))]
+public class CognitoClientFactoryTests
+{
+ [Fact]
+ public void CreateClient_WithValidSettings_ShouldReturnAmazonCognitoIdentityProviderClient ()
+ {
+ // Arrange
+ var settingsMock = new Mock>();
+ settingsMock.Setup(s => s.Value).Returns(new CognitoSettings
+ {
+ ClientId = "test-client-id",
+ ClientSecret = "test-client-secret",
+ Region = "us-west-2"
+ });
+
+ var factory = new CognitoClientFactory(settingsMock.Object);
+
+ // Act
+ var client = factory.CreateClient();
+
+ // Assert
+ client.Should().NotBeNull().And.BeOfType();
+ }
+}
diff --git a/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoUserManagerTest.cs b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoUserManagerTest.cs
new file mode 100644
index 0000000..e8a451d
--- /dev/null
+++ b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/CognitoUserManagerTest.cs
@@ -0,0 +1,108 @@
+using Amazon.CognitoIdentityProvider;
+using Amazon.CognitoIdentityProvider.Model;
+using FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Factory;
+using FIAP.TechChallenge.ByteMeBurger.Domain.Entities;
+using FluentAssertions;
+using FluentAssertions.Execution;
+using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
+using Moq;
+
+namespace FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test;
+
+[TestSubject(typeof(CognitoUserManager))]
+public class CognitoUserManagerTest
+{
+ private readonly Mock _cognitoClientMock;
+ private readonly CognitoUserManager _userManager;
+
+ public CognitoUserManagerTest()
+ {
+ // Arrange
+ var mockFactory = new Mock();
+ _cognitoClientMock = new Mock();
+ var settings = Options.Create(new CognitoSettings
+ { UserPoolId = "testPoolId", UserPoolClientId = "testClientId" });
+
+ mockFactory.Setup(f => f.CreateClient()).Returns(_cognitoClientMock.Object);
+
+ _userManager = new CognitoUserManager(mockFactory.Object, settings);
+ }
+
+ [Fact]
+ public async Task FindByCpfAsync_ShouldReturnCustomer_WhenUserExists()
+ {
+ // Arrange
+ var cpf = "28642827041";
+ var response = new AdminGetUserResponse
+ {
+ UserAttributes =
+ [
+ new AttributeType { Name = "email", Value = "test@example.com" },
+ new AttributeType { Name = "name", Value = "Test User" },
+ new AttributeType { Name = "sub", Value = Guid.NewGuid().ToString() }
+ ]
+ };
+
+ _cognitoClientMock.Setup(c => c.AdminGetUserAsync(It.IsAny(), default))
+ .ReturnsAsync(response);
+
+ // Act
+ var result = await _userManager.FindByCpfAsync(cpf);
+
+ // Assert
+ using (new AssertionScope())
+ {
+ result.Should().NotBeNull();
+ result.Cpf.Value.Should().Be(cpf);
+ result.Email.Should().Be("test@example.com");
+ result.Name.Should().Be("Test User");
+ result.Id.Should().NotBeEmpty();
+ }
+ }
+
+ [Fact]
+ public async Task FindByCpfAsync_ShouldReturnNull_WhenUserNotFound()
+ {
+ // Arrange
+ const string cpf = "123456789";
+
+ _cognitoClientMock.Setup(c => c.AdminGetUserAsync(It.IsAny(), default))
+ .ThrowsAsync(new UserNotFoundException("User not found"));
+
+ // Act
+ var result = await _userManager.FindByCpfAsync(cpf);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task CreateAsync_ShouldReturnCustomer_WhenUserIsCreated()
+ {
+ // Arrange
+ var customer = new Customer(Guid.NewGuid(), "28642827041", "Test User", "test@example.com");
+
+ _cognitoClientMock.Setup(c => c.AdminCreateUserAsync(It.IsAny(), default))
+ .ReturnsAsync(new AdminCreateUserResponse
+ {
+ User = new UserType
+ {
+ Attributes = [new AttributeType() { Name = "sub", Value = customer.Id.ToString() }]
+ }
+ });
+
+ // Act
+ var result = await _userManager.CreateAsync(customer);
+
+ // Assert
+ using (new AssertionScope())
+ {
+ result.Should().NotBeNull();
+ result.Cpf.Should().Be(customer.Cpf);
+ result.Name.Should().Be(customer.Name);
+ result.Email.Should().Be(customer.Email);
+ result.Id.Should().NotBeEmpty();
+ }
+ }
+}
diff --git a/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test.csproj b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test.csproj
new file mode 100644
index 0000000..c48efbc
--- /dev/null
+++ b/tests/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test/FIAP.TechChallenge.ByteMeBurger.Cognito.Gateway.Test.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tf/main.tf b/tf/main.tf
index 1b13482..aff8078 100644
--- a/tf/main.tf
+++ b/tf/main.tf
@@ -19,6 +19,18 @@ data "aws_eks_cluster" "techchallenge_cluster" {
# api_id = tolist(data.aws_apigatewayv2_apis.api_id.ids)[0]
# }
+##############################
+# COGNITO USER POOL
+##############################
+
+data "aws_cognito_user_pools" "user_pool" {
+ name = var.user_pool_name
+}
+
+data "aws_cognito_user_pool_clients" "api_client" {
+ user_pool_id = data.aws_cognito_user_pools.user_pool.ids[0]
+}
+
##############################
# SQS
##############################
@@ -39,11 +51,13 @@ data "aws_rds_cluster" "example" {
}
locals {
- connection_string = "Server=${data.aws_rds_cluster.example.endpoint};Database=${data.aws_rds_cluster.example.database_name};Uid=${var.db_user};Pwd=${var.db_pwd};Port=${data.aws_rds_cluster.example.port};"
- jwt_issuer = var.jwt_issuer
- jwt_aud = var.jwt_aud
- docker_image = var.api_docker_image
- events_queue_name = aws_sqs_queue.bmb-events.name
+ connection_string = "Server=${data.aws_rds_cluster.example.endpoint};Database=${data.aws_rds_cluster.example.database_name};Uid=${var.db_user};Pwd=${var.db_pwd};Port=${data.aws_rds_cluster.example.port};"
+ jwt_issuer = var.jwt_issuer
+ jwt_aud = var.jwt_aud
+ docker_image = var.api_docker_image
+ events_queue_name = aws_sqs_queue.bmb-events.name
+ cognito_user_pool_id = data.aws_cognito_user_pools.user_pool.ids[0]
+ cognito_user_pool_client_id = data.aws_cognito_user_pool_clients.api_client.client_ids[0]
}
@@ -81,8 +95,14 @@ resource "kubernetes_config_map_v1" "config_map_api" {
"SqsSettings__QueueName" = local.events_queue_name
"SqsSettings__Enabled" = true
"SqsSettings__Region" = "us-east-1"
- "SqsSettings__ClientId" = var.access_key_id
- "SqsSettings__ClientSecret" = var.secret_access_key
+ "SqsSettings__ClientId" = var.api_access_key_id
+ "SqsSettings__ClientSecret" = var.api_secret_access_key
+ "CognitoSettings__UserPoolId" = local.cognito_user_pool_id
+ "CognitoSettings__UserPoolClientId" = local.cognito_user_pool_client_id
+ "CognitoSettings__Enabled" = true
+ "CognitoSettings__Region" = "us-east-1"
+ "CognitoSettings__ClientId" = var.api_access_key_id
+ "CognitoSettings__ClientSecret" = var.api_secret_access_key
}
}
diff --git a/tf/output.tf b/tf/output.tf
index d429e7f..2cc9c3c 100644
--- a/tf/output.tf
+++ b/tf/output.tf
@@ -2,3 +2,12 @@ output "eks_cluster" {
value = data.aws_eks_cluster.techchallenge_cluster
sensitive = true
}
+
+output "userpool_id" {
+ value = local.cognito_user_pool_id
+
+}
+
+output "api_client_id" {
+ value = local.cognito_user_pool_client_id
+}
diff --git a/tf/variables.tf b/tf/variables.tf
index 96eb07f..bf8678c 100644
--- a/tf/variables.tf
+++ b/tf/variables.tf
@@ -74,14 +74,19 @@ variable "db_pwd" {
default = "db_password"
}
-variable "access_key_id" {
+variable "api_access_key_id" {
type = string
nullable = false
sensitive = true
}
-variable "secret_access_key" {
+variable "api_secret_access_key" {
type = string
nullable = false
sensitive = true
}
+
+variable "user_pool_name" {
+ type = string
+ default = "bmb-users-pool-local"
+}