From 652d7e053832a67a914977b12781c09966d176c7 Mon Sep 17 00:00:00 2001 From: soulbrawl <142645514+soulbrawl@users.noreply.github.com> Date: Fri, 13 Dec 2024 01:42:42 -0300 Subject: [PATCH 1/3] API Feature: DeleteCustomerById() under /costumers/deleteById using the new generic DeleteById() --- .gitignore | 1 + .../Controllers/CustomerController.cs | 15 ++++++++++++++- .../Interfaces/ICustomerService.cs | 1 + .../Services/CustomerService.cs | 7 +++++++ .../Interfaces/IDapperEntity.cs | 1 + .../Persistance/DapperEntity.cs | 7 +++++++ 6 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1348597..d08b490 100644 --- a/.gitignore +++ b/.gitignore @@ -398,3 +398,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml +/.idea diff --git a/src/ThinkCoreBE.Api/Controllers/CustomerController.cs b/src/ThinkCoreBE.Api/Controllers/CustomerController.cs index bebc7d8..dc50714 100644 --- a/src/ThinkCoreBE.Api/Controllers/CustomerController.cs +++ b/src/ThinkCoreBE.Api/Controllers/CustomerController.cs @@ -1,4 +1,5 @@ -using ThinkCoreBE.Application.Interfaces; +using Microsoft.AspNetCore.Mvc; +using ThinkCoreBE.Application.Interfaces; namespace ThinkCoreBE.Api.Controllers { @@ -9,6 +10,10 @@ public static void AddCustomerEndpoints(this WebApplication app) app.MapGet("/customers/getAll", GetAllCustomers) .WithName("GetAllCustomers") .WithOpenApi(); + + app.MapDelete("/customers/deleteById", DeleteCustomerById) + .WithName("DeleteCustomerById") + .WithOpenApi(); } private static async Task GetAllCustomers(ICustomerService customerService, CancellationToken cancellationToken) @@ -17,5 +22,13 @@ private static async Task GetAllCustomers(ICustomerService customerServ return Results.Ok(customers); } + private static async Task DeleteCustomerById([FromQuery] long id, ICustomerService customerService, CancellationToken cancellationToken) + { + var deletedCount = await customerService.DeleteCustomerByIdAsync(id, cancellationToken); + if (deletedCount > 0) + return Results.Ok(new { Message = "Customer deleted successfully." }); + else + return Results.NotFound(new { Message = "Customer not found." }); + } } } diff --git a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs index 7829085..ae519d9 100644 --- a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs +++ b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs @@ -5,5 +5,6 @@ namespace ThinkCoreBE.Application.Interfaces public interface ICustomerService { public Task> GetAllCustomersAsync(CancellationToken cancellationToken); + public Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken); } } diff --git a/src/ThinkCoreBE.Application/Services/CustomerService.cs b/src/ThinkCoreBE.Application/Services/CustomerService.cs index f202676..3551fc9 100644 --- a/src/ThinkCoreBE.Application/Services/CustomerService.cs +++ b/src/ThinkCoreBE.Application/Services/CustomerService.cs @@ -14,5 +14,12 @@ public async Task> GetAllCustomersAsync(CancellationToken { return await _context.Customers.GetAllAsync(cancellationToken); } + + public async Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken = default) + { + const string idColumnName = "CustomerId"; + var deletedCount = await _context.Customers.DeleteByIdAsync(id, idColumnName, cancellationToken); + return deletedCount; + } } } diff --git a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs index 42c9265..9fcdad3 100644 --- a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs +++ b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs @@ -3,5 +3,6 @@ public interface IDapperEntity { public Task> GetAllAsync(CancellationToken cancellationToken); + public Task DeleteByIdAsync(long id, string idColumnName, CancellationToken cancellationToken); } } diff --git a/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs b/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs index adb042c..18bd7fe 100644 --- a/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs +++ b/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs @@ -18,5 +18,12 @@ public async Task> GetAllAsync(CancellationToken cancellationToke var sql = $"SELECT * FROM \"{typeof(T).Name}s\""; return await _connection.QueryAsync(sql, cancellationToken); } + + public async Task DeleteByIdAsync(long id, string idColumnName, CancellationToken cancellationToken = default) + { + var sql = $"DELETE FROM \"{typeof(T).Name}s\" WHERE \"{idColumnName}\" = @Id"; + var deletedCount = await Task.Run(async () => await _connection.ExecuteAsync(sql, new { Id = id }), cancellationToken); + return deletedCount; + } } } From 8e5f75ef66dbf56e733489eef55631bee96d65a1 Mon Sep 17 00:00:00 2001 From: soulbrawl <142645514+soulbrawl@users.noreply.github.com> Date: Fri, 13 Dec 2024 23:15:56 -0300 Subject: [PATCH 2/3] PR small requested fixes --- .../Interfaces/ICustomerService.cs | 1 + src/ThinkCoreBE.Application/Services/CustomerService.cs | 5 ++--- src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs | 3 ++- .../Persistance/DapperEntity.cs | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs index ae519d9..c99e100 100644 --- a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs +++ b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs @@ -5,6 +5,7 @@ namespace ThinkCoreBE.Application.Interfaces public interface ICustomerService { public Task> GetAllCustomersAsync(CancellationToken cancellationToken); + public Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken); } } diff --git a/src/ThinkCoreBE.Application/Services/CustomerService.cs b/src/ThinkCoreBE.Application/Services/CustomerService.cs index 3551fc9..ed0c4fa 100644 --- a/src/ThinkCoreBE.Application/Services/CustomerService.cs +++ b/src/ThinkCoreBE.Application/Services/CustomerService.cs @@ -17,9 +17,8 @@ public async Task> GetAllCustomersAsync(CancellationToken public async Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken = default) { - const string idColumnName = "CustomerId"; - var deletedCount = await _context.Customers.DeleteByIdAsync(id, idColumnName, cancellationToken); - return deletedCount; + var affectedRows = await _context.Customers.DeleteByIdAsync(id, cancellationToken); + return affectedRows; } } } diff --git a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs index 9fcdad3..84db192 100644 --- a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs +++ b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs @@ -3,6 +3,7 @@ public interface IDapperEntity { public Task> GetAllAsync(CancellationToken cancellationToken); - public Task DeleteByIdAsync(long id, string idColumnName, CancellationToken cancellationToken); + + public Task DeleteByIdAsync(long id, CancellationToken cancellationToken); } } diff --git a/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs b/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs index 18bd7fe..187519e 100644 --- a/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs +++ b/src/ThinkCoreBE.Infrastructure/Persistance/DapperEntity.cs @@ -19,11 +19,11 @@ public async Task> GetAllAsync(CancellationToken cancellationToke return await _connection.QueryAsync(sql, cancellationToken); } - public async Task DeleteByIdAsync(long id, string idColumnName, CancellationToken cancellationToken = default) + public async Task DeleteByIdAsync(long id, CancellationToken cancellationToken = default) { - var sql = $"DELETE FROM \"{typeof(T).Name}s\" WHERE \"{idColumnName}\" = @Id"; - var deletedCount = await Task.Run(async () => await _connection.ExecuteAsync(sql, new { Id = id }), cancellationToken); - return deletedCount; + var sql = $"DELETE FROM \"{typeof(T).Name}s\" WHERE \"{typeof(T).Name}Id\" = @Id"; + var affectedRows = await Task.Run(async () => await _connection.ExecuteAsync(sql, new { Id = id }), cancellationToken); + return affectedRows; } } } From 8f576fc9ed61d7f89a453f7f89b95246fe94c540 Mon Sep 17 00:00:00 2001 From: soulbrawl <142645514+soulbrawl@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:41:07 -0300 Subject: [PATCH 3/3] implemented request changes: Result wrapper for Services and EndpointFilter as handler --- .../Controllers/CustomerController.cs | 24 +++++---- src/ThinkCoreBE.Api/Program.cs | 3 ++ src/ThinkCoreBE.Api/ResultEndpointFilter.cs | 53 +++++++++++++++++++ .../Interfaces/ICustomerService.cs | 4 +- src/ThinkCoreBE.Application/Result.cs | 17 ++++++ .../Services/CustomerService.cs | 34 ++++++++++-- 6 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 src/ThinkCoreBE.Api/ResultEndpointFilter.cs create mode 100644 src/ThinkCoreBE.Application/Result.cs diff --git a/src/ThinkCoreBE.Api/Controllers/CustomerController.cs b/src/ThinkCoreBE.Api/Controllers/CustomerController.cs index dc50714..311d43b 100644 --- a/src/ThinkCoreBE.Api/Controllers/CustomerController.cs +++ b/src/ThinkCoreBE.Api/Controllers/CustomerController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Mvc; +using ThinkCoreBE.Application; using ThinkCoreBE.Application.Interfaces; +using ThinkCoreBE.Domain.Entities; namespace ThinkCoreBE.Api.Controllers { @@ -8,27 +10,31 @@ public static class CustomerController public static void AddCustomerEndpoints(this WebApplication app) { app.MapGet("/customers/getAll", GetAllCustomers) + .AddEndpointFilter() .WithName("GetAllCustomers") .WithOpenApi(); app.MapDelete("/customers/deleteById", DeleteCustomerById) + .AddEndpointFilter() .WithName("DeleteCustomerById") .WithOpenApi(); } - private static async Task GetAllCustomers(ICustomerService customerService, CancellationToken cancellationToken) + private static async Task>> GetAllCustomers( + ICustomerService customerService, + CancellationToken cancellationToken) { - var customers = await customerService.GetAllCustomersAsync(cancellationToken); - return Results.Ok(customers); + // Return a Result (the filter handles final HTTP mapping) + return await customerService.GetAllCustomersAsync(cancellationToken); } - private static async Task DeleteCustomerById([FromQuery] long id, ICustomerService customerService, CancellationToken cancellationToken) + private static async Task> DeleteCustomerById( + [FromQuery] long id, + ICustomerService customerService, + CancellationToken cancellationToken) { - var deletedCount = await customerService.DeleteCustomerByIdAsync(id, cancellationToken); - if (deletedCount > 0) - return Results.Ok(new { Message = "Customer deleted successfully." }); - else - return Results.NotFound(new { Message = "Customer not found." }); + // Returns a simple success/fail message + return await customerService.DeleteCustomerByIdAsync(id, cancellationToken); } } } diff --git a/src/ThinkCoreBE.Api/Program.cs b/src/ThinkCoreBE.Api/Program.cs index 157e9ab..49a4e4d 100644 --- a/src/ThinkCoreBE.Api/Program.cs +++ b/src/ThinkCoreBE.Api/Program.cs @@ -1,5 +1,6 @@ using FluentMigrator.Runner; using Microsoft.AspNetCore.Authentication.Negotiate; +using ThinkCoreBE.Api; using ThinkCoreBE.Api.Controllers; using ThinkCoreBE.Application; using ThinkCoreBE.Infrastructure; @@ -17,6 +18,8 @@ options.FallbackPolicy = options.DefaultPolicy; }); +builder.Services.AddScoped(); + builder.Services.AddFluentMigratorCore() .ConfigureRunner(runner => runner .AddPostgres() diff --git a/src/ThinkCoreBE.Api/ResultEndpointFilter.cs b/src/ThinkCoreBE.Api/ResultEndpointFilter.cs new file mode 100644 index 0000000..ca93b10 --- /dev/null +++ b/src/ThinkCoreBE.Api/ResultEndpointFilter.cs @@ -0,0 +1,53 @@ +using ThinkCoreBE.Application; + +namespace ThinkCoreBE.Api +{ + // An IEndpointFilter that converts a Result object returned from a minimal API endpoint into an HTTP IResult. + public class ResultEndpointFilter : IEndpointFilter + { + public async ValueTask InvokeAsync( + EndpointFilterInvocationContext context, + EndpointFilterDelegate next) + { + // Execute the endpoint (or next filter) and capture the result + var methodResult = await next(context); + + // If the endpoint already returns IResult, no extra handling needed + if (methodResult is IResult directIResult) + return directIResult; + + // If the endpoint result is a Result, map it to IResult + if (methodResult != null && IsResultOfT(methodResult)) + { + return ConvertResultToIResult(methodResult); + } + + // If it's neither, return as-is + return methodResult; + } + + private static bool IsResultOfT(object obj) + { + var type = obj.GetType(); + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Result<>); + } + + private static IResult ConvertResultToIResult(object resultObj) + { + var type = resultObj.GetType(); + + bool? success = type.GetProperty(nameof(Result.Success)) + ?.GetValue(resultObj) as bool?; + if (success == true) + { + var content = type.GetProperty(nameof(Result.Content))?.GetValue(resultObj); + return Results.Ok(content); + } + else + { + var errorMsg = type.GetProperty(nameof(Result.ErrorMessage))?.GetValue(resultObj) as string; + return Results.NotFound(new { Error = errorMsg }); + } + } + } +} diff --git a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs index c99e100..bc57f05 100644 --- a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs +++ b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs @@ -4,8 +4,8 @@ namespace ThinkCoreBE.Application.Interfaces { public interface ICustomerService { - public Task> GetAllCustomersAsync(CancellationToken cancellationToken); + public Task>> GetAllCustomersAsync(CancellationToken cancellationToken); - public Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken); + public Task> DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken); } } diff --git a/src/ThinkCoreBE.Application/Result.cs b/src/ThinkCoreBE.Application/Result.cs new file mode 100644 index 0000000..cf068ed --- /dev/null +++ b/src/ThinkCoreBE.Application/Result.cs @@ -0,0 +1,17 @@ +namespace ThinkCoreBE.Application +{ + // Generic result wrapper for service-layer operations. + /// Type of data returned by the service. + public class Result // + { + public bool Success { get; set; } + + public T? Content { get; set; } + + public string? ErrorMessage { get; set; } + + public static Result Ok(T content) => new() { Success = true, Content = content }; + + public static Result Fail(string errorMessage) => new() { Success = false, ErrorMessage = errorMessage }; + } +} diff --git a/src/ThinkCoreBE.Application/Services/CustomerService.cs b/src/ThinkCoreBE.Application/Services/CustomerService.cs index ed0c4fa..3c70c30 100644 --- a/src/ThinkCoreBE.Application/Services/CustomerService.cs +++ b/src/ThinkCoreBE.Application/Services/CustomerService.cs @@ -10,15 +10,39 @@ public sealed class CustomerService : ICustomerService public CustomerService(IThinkCoreDbContext context) { _context = context; } - public async Task> GetAllCustomersAsync(CancellationToken cancellationToken = default) + public async Task>> GetAllCustomersAsync(CancellationToken cancellationToken = default) { - return await _context.Customers.GetAllAsync(cancellationToken); + try + { + var customers = await _context.Customers.GetAllAsync(cancellationToken); + return Result>.Ok(customers); + } + catch (Exception ex) + { + // Log if needed + return Result>.Fail($"Error fetching customers: {ex.Message}"); + } } - public async Task DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken = default) + public async Task> DeleteCustomerByIdAsync(long id, CancellationToken cancellationToken = default) { - var affectedRows = await _context.Customers.DeleteByIdAsync(id, cancellationToken); - return affectedRows; + try + { + var affectedRows = await _context.Customers.DeleteByIdAsync(id, cancellationToken); + if (affectedRows > 0) + { + return Result.Ok("Customer deleted successfully."); + } + else + { + return Result.Fail("Customer not found"); + } + } + catch (Exception ex) + { + // Log if needed + return Result.Fail($"Error deleting customer: {ex.Message}"); + } } } }