From b529d905b12fff6033668a08005b000f4920069e Mon Sep 17 00:00:00 2001 From: Efrain Marin Date: Tue, 5 Aug 2025 22:14:27 -0600 Subject: [PATCH] feat: added endpoint DeleteProduct - fix: adapters package updated - fix: status property renamed --- .../Adapter/DeleteProductResponseAdapter.cs | 7 +++ .../UseCases/Product/Adapter/ProductPort.cs | 4 ++ .../Product/Input/CreateProductRequest.cs | 2 +- .../Product/Input/DeleteProductRequest.cs | 14 ++++++ .../Product/Input/UpdateProductRequest.cs | 2 +- .../UseCases/Product/Ports/IProductPort.cs | 2 + .../UseCases/Product/ProductHandler.cs | 41 +++++++++++++++-- .../Validator/DeleteProductValidator.cs | 13 ++++++ .../Clients/IInventoryServiceClient.cs | 3 ++ .../Clients/Requests/ProductRequest.cs | 2 +- .../Core.Inventory.External.csproj | 2 +- .../Controllers/ProductController.cs | 45 +++++++++++++++++++ .../Extensions/ServiceCollectionExtension.cs | 4 ++ 13 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 Core.Inventory.Application/UseCases/Product/Adapter/DeleteProductResponseAdapter.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/DeleteProductRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Validator/DeleteProductValidator.cs diff --git a/Core.Inventory.Application/UseCases/Product/Adapter/DeleteProductResponseAdapter.cs b/Core.Inventory.Application/UseCases/Product/Adapter/DeleteProductResponseAdapter.cs new file mode 100644 index 0000000..b7ee57e --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Adapter/DeleteProductResponseAdapter.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.Application.UseCases.Product.Adapter +{ + public class DeleteProductResponseAdapter + { + public bool Success { get; init; } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs b/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs index a202e7c..2de9b95 100644 --- a/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs +++ b/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs @@ -15,5 +15,9 @@ namespace Core.Inventory.Application.UseCases.Product.Adapter { ViewModel = new OkObjectResult(output); } + public void Success(DeleteProductResponseAdapter output) + { + ViewModel = new OkObjectResult(output); + } } } \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs index fb023f2..f9e3d30 100644 --- a/Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs +++ b/Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs @@ -7,7 +7,7 @@ namespace Core.Inventory.Application.UseCases.Product.Input public string TenantId { get; set; } = null!; public string ProductName { get; set; } = null!; public string Description { get; set; } = null!; - public string Status { get; set; } = null!; + public string ProductStatus { get; set; } = null!; public List TagIds { get; set; } = new List(); public bool Validate() diff --git a/Core.Inventory.Application/UseCases/Product/Input/DeleteProductRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/DeleteProductRequest.cs new file mode 100644 index 0000000..ce9c564 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/DeleteProductRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class DeleteProductRequest : Notificator, ICommand + { + public string Id { get; set; } = null!; + + public bool Validate() + { + return Id != null; + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs index dffb364..7a47331 100644 --- a/Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs +++ b/Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs @@ -9,7 +9,7 @@ namespace Core.Inventory.Application.UseCases.Product.Input public string TenantId { get; set; } = null!; public string ProductName { get; set; } = null!; public string Description { get; set; } = null!; - public string Status { get; set; } = null!; + public string ProductStatus { get; set; } = null!; public List TagIds { get; set; } = new List(); public bool Validate() diff --git a/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs b/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs index 6ffa65d..461e26b 100644 --- a/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs +++ b/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs @@ -1,4 +1,5 @@ using Core.Adapters.Lib.Inventory; +using Core.Inventory.Application.UseCases.Product.Adapter; using Lib.Architecture.BuildingBlocks; namespace Core.Inventory.Application.UseCases.Product.Ports @@ -6,6 +7,7 @@ namespace Core.Inventory.Application.UseCases.Product.Ports public interface IProductPort : IBasePort, ICommandSuccessPort, ICommandSuccessPort>, + ICommandSuccessPort, INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort, INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort, IBadRequestPort diff --git a/Core.Inventory.Application/UseCases/Product/ProductHandler.cs b/Core.Inventory.Application/UseCases/Product/ProductHandler.cs index 9cd08fa..f8b82d9 100644 --- a/Core.Inventory.Application/UseCases/Product/ProductHandler.cs +++ b/Core.Inventory.Application/UseCases/Product/ProductHandler.cs @@ -1,4 +1,5 @@ using Core.Adapters.Lib.Inventory; +using Core.Inventory.Application.UseCases.Product.Adapter; using Core.Inventory.Application.UseCases.Product.Input; using Core.Inventory.Application.UseCases.Product.Ports; using Core.Inventory.External.Clients; @@ -15,13 +16,15 @@ namespace Core.Inventory.Application.UseCases.Product IComponentHandler, IComponentHandler, IComponentHandler, - IComponentHandler + IComponentHandler, + IComponentHandler { private readonly IProductPort _port; private readonly IValidator _changeProductStatusValidator; private readonly IValidator _registerProductValidator; private readonly IValidator _updateProductValidator; private readonly IValidator _productsByListValidator; + private readonly IValidator _deleteProductValidator; private readonly IInventoryServiceClient _inventoryServiceClient; public ProductHandler( @@ -30,6 +33,7 @@ namespace Core.Inventory.Application.UseCases.Product IValidator registerProductValidator, IValidator updateProductValidator, IValidator productsByListValidator, + IValidator deleteProductValidator, IInventoryServiceClient inventoryDALService) { _port = port ?? throw new ArgumentNullException(nameof(port)); @@ -38,6 +42,7 @@ namespace Core.Inventory.Application.UseCases.Product _updateProductValidator = updateProductValidator ?? throw new ArgumentNullException(nameof(updateProductValidator)); _inventoryServiceClient = inventoryDALService ?? throw new ArgumentNullException(nameof(inventoryDALService)); _productsByListValidator = productsByListValidator ?? throw new ArgumentNullException(nameof(productsByListValidator)); + _deleteProductValidator = deleteProductValidator ?? throw new ArgumentNullException(nameof(deleteProductValidator)); } public async ValueTask ExecuteAsync(GetProductRequest command, CancellationToken cancellationToken = default) @@ -153,7 +158,7 @@ namespace Core.Inventory.Application.UseCases.Product TenantId = command.TenantId, ProductName = command.ProductName, Description = command.Description, - Status = command.Status, + ProductStatus = command.ProductStatus, TagIds = command.TagIds }; @@ -191,7 +196,7 @@ namespace Core.Inventory.Application.UseCases.Product TenantId = command.TenantId, ProductName = command.ProductName, Description = command.Description, - Status = Enum.Parse(command.Status), + ProductStatus = Enum.Parse(command.ProductStatus), TagIds = command.TagIds.Select(id => MongoDB.Bson.ObjectId.Parse(id)).ToList() }; @@ -210,5 +215,33 @@ namespace Core.Inventory.Application.UseCases.Product ApiResponseHelper.EvaluatePort(ex, _port); } } + + public async ValueTask ExecuteAsync(DeleteProductRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_deleteProductValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var result = await _inventoryServiceClient.DeleteProductAsync(command.Id, cancellationToken).ConfigureAwait(false); + + if (!result) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(new DeleteProductResponseAdapter() { Success = true }); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Validator/DeleteProductValidator.cs b/Core.Inventory.Application/UseCases/Product/Validator/DeleteProductValidator.cs new file mode 100644 index 0000000..4c10b0e --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Validator/DeleteProductValidator.cs @@ -0,0 +1,13 @@ +using Core.Inventory.Application.UseCases.Product.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Product.Validator +{ + public class DeleteProductValidator : AbstractValidator + { + public DeleteProductValidator() + { + RuleFor(i => i.Id).NotEmpty().NotNull().OverridePropertyName(x => x.Id).WithName("Product Id").WithMessage("Product Id is Obligatory."); + } + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/IInventoryServiceClient.cs b/Core.Inventory.External/Clients/IInventoryServiceClient.cs index 72c254c..e619db8 100644 --- a/Core.Inventory.External/Clients/IInventoryServiceClient.cs +++ b/Core.Inventory.External/Clients/IInventoryServiceClient.cs @@ -129,6 +129,9 @@ namespace Core.Inventory.External.Clients [Delete("/api/v1/Product/{productId}/tags/{tagId}")] Task RemoveTagFromProductAsync([FromRoute] string productId, [FromRoute] string tagId, CancellationToken cancellationToken = default); + [Delete("/api/v1/Product/{id}")] + Task DeleteProductAsync([FromRoute] string id, CancellationToken cancellationToken = default); + #endregion } } diff --git a/Core.Inventory.External/Clients/Requests/ProductRequest.cs b/Core.Inventory.External/Clients/Requests/ProductRequest.cs index b170e40..fe116f7 100644 --- a/Core.Inventory.External/Clients/Requests/ProductRequest.cs +++ b/Core.Inventory.External/Clients/Requests/ProductRequest.cs @@ -5,7 +5,7 @@ namespace Core.Inventory.External.Clients.Requests public string TenantId { get; set; } = null!; public string ProductName { get; set; } = null!; public string Description { get; set; } = null!; - public string Status { get; set; } = null!; + public string ProductStatus { get; set; } = null!; public List TagIds { get; set; } = new List(); } } \ No newline at end of file diff --git a/Core.Inventory.External/Core.Inventory.External.csproj b/Core.Inventory.External/Core.Inventory.External.csproj index 011ea64..2c5fc6b 100644 --- a/Core.Inventory.External/Core.Inventory.External.csproj +++ b/Core.Inventory.External/Core.Inventory.External.csproj @@ -7,7 +7,7 @@ - + diff --git a/Core.Inventory.Service.API/Controllers/ProductController.cs b/Core.Inventory.Service.API/Controllers/ProductController.cs index 4f31a35..47fe05f 100644 --- a/Core.Inventory.Service.API/Controllers/ProductController.cs +++ b/Core.Inventory.Service.API/Controllers/ProductController.cs @@ -24,6 +24,7 @@ namespace Core.Inventory.Service.API.Controllers private readonly IComponentHandler createProductHandler; private readonly IComponentHandler updateProductHandler; private readonly IComponentHandler changeProductStatusHandler; + private readonly IComponentHandler deleteProductHandler; private readonly IProductPort port; /// @@ -36,6 +37,7 @@ namespace Core.Inventory.Service.API.Controllers IComponentHandler createProductHandler, IComponentHandler updateProductHandler, IComponentHandler changeProductStatusHandler, + IComponentHandler deleteProductHandler, IProductPort port ) { @@ -45,6 +47,7 @@ namespace Core.Inventory.Service.API.Controllers this.getAllProductsHandler = getAllProductsHandler; this.getProductHandler = getProductHandler; this.getAllProductsByListHandler = getAllProductsByListHandler; + this.deleteProductHandler = deleteProductHandler; this.port = port; } @@ -165,6 +168,15 @@ namespace Core.Inventory.Service.API.Controllers /// /// Changes the status of the Product. /// + /// The request containing the product ID and new ProductStatus. + /// The updated entity. + /// The Product updates. + /// The Product not found. + /// The Product could not be updated. + /// The Product could not be updated. + /// The Product could not be updated. + /// The Product could not be updated. + /// The service internal error. [HttpPatch] [Route("ChangeStatus")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -183,5 +195,38 @@ namespace Core.Inventory.Service.API.Controllers return port.ViewModel; } + + /// + /// Deletes a Product by its MongoDB identifier. + /// + /// The request containing the product ID to delete. + /// Cancellation token for the asynchronous operation. + /// The representing the result of the service call. + /// The Product deleted successfully. + /// No content if the Product was not found. + /// Bad request if the Product ID is missing or invalid. + /// Unauthorized if the user is not authenticated. + /// Precondition failed if the request does not meet expected conditions. + /// Unprocessable entity if the request cannot be processed. + /// Internal server error if an unexpected error occurs. + [HttpDelete("Delete")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task DeleteProductAsync([FromBody] DeleteProductRequest request, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(request.Id)) + { + return BadRequest("Invalid Product identifier"); + } + + await deleteProductHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } } } \ No newline at end of file diff --git a/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs b/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs index bfbd1b6..db37fe7 100644 --- a/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs +++ b/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs @@ -154,6 +154,10 @@ namespace Core.Inventory.Service.API.Extensions services.AddScoped, ChangeProductStatusValidator>(); services.AddScoped, ProductHandler>(); + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, DeleteProductValidator>(); + services.AddScoped, ProductHandler>(); + #endregion return services; -- 2.49.1