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..311d43b 100644 --- a/src/ThinkCoreBE.Api/Controllers/CustomerController.cs +++ b/src/ThinkCoreBE.Api/Controllers/CustomerController.cs @@ -1,4 +1,7 @@ -using ThinkCoreBE.Application.Interfaces; +using Microsoft.AspNetCore.Mvc; +using ThinkCoreBE.Application; +using ThinkCoreBE.Application.Interfaces; +using ThinkCoreBE.Domain.Entities; namespace ThinkCoreBE.Api.Controllers { @@ -7,15 +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) + { + // 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 7829085..bc57f05 100644 --- a/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs +++ b/src/ThinkCoreBE.Application/Interfaces/ICustomerService.cs @@ -4,6 +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); } } 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 f202676..3c70c30 100644 --- a/src/ThinkCoreBE.Application/Services/CustomerService.cs +++ b/src/ThinkCoreBE.Application/Services/CustomerService.cs @@ -10,9 +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) + { + 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}"); + } } } } diff --git a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs index 42c9265..84db192 100644 --- a/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs +++ b/src/ThinkCoreBE.Domain/Interfaces/IDapperEntity.cs @@ -3,5 +3,7 @@ public interface IDapperEntity { public Task> GetAllAsync(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 adb042c..187519e 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, CancellationToken cancellationToken = default) + { + 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; + } } }