From c446fa652e2da444d0c7e426e434166a661ed549 Mon Sep 17 00:00:00 2001 From: Efrain Marin Date: Sun, 3 Aug 2025 18:56:47 -0600 Subject: [PATCH 1/2] feat: Added Product controller and endpoints (Service Layer) --- .../UseCases/Product/Adapter/ProductPort.cs | 19 ++ .../Input/ChangeProductStatusRequest.cs | 16 ++ .../Product/Input/CreateProductRequest.cs | 18 ++ .../Input/GetAllProductsByListRequest.cs | 14 ++ .../Product/Input/GetAllProductsRequest.cs | 12 + .../Product/Input/GetProductRequest.cs | 14 ++ .../Product/Input/UpdateProductRequest.cs | 20 ++ .../UseCases/Product/Ports/IProductPort.cs | 14 ++ .../UseCases/Product/ProductHandler.cs | 214 ++++++++++++++++++ .../Validator/ChangeProductStatusValidator.cs | 13 ++ .../Validator/CreateProductValidator.cs | 14 ++ .../GetAllProductsByListValidator.cs | 13 ++ .../Validator/UpdateProductValidator.cs | 14 ++ .../Clients/IInventoryServiceClient.cs | 29 +++ .../Clients/Requests/ProductRequest.cs | 11 + .../Core.Inventory.External.csproj | 2 +- .../Controllers/ProductController.cs | 187 +++++++++++++++ .../Extensions/ServiceCollectionExtension.cs | 29 +++ .../appsettings.Local.json | 3 +- 19 files changed, 654 insertions(+), 2 deletions(-) create mode 100644 Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/ChangeProductStatusRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/GetAllProductsByListRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/GetAllProductsRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/GetProductRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs create mode 100644 Core.Inventory.Application/UseCases/Product/ProductHandler.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Validator/ChangeProductStatusValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Validator/CreateProductValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Validator/GetAllProductsByListValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Product/Validator/UpdateProductValidator.cs create mode 100644 Core.Inventory.External/Clients/Requests/ProductRequest.cs create mode 100644 Core.Inventory.Service.API/Controllers/ProductController.cs diff --git a/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs b/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs new file mode 100644 index 0000000..a202e7c --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Adapter/ProductPort.cs @@ -0,0 +1,19 @@ +using Core.Adapters.Lib.Inventory; +using Core.Inventory.Application.UseCases.Product.Ports; +using Lib.Architecture.BuildingBlocks; +using Microsoft.AspNetCore.Mvc; + +namespace Core.Inventory.Application.UseCases.Product.Adapter +{ + public class ProductPort : BasePresenter, IProductPort + { + public void Success(ProductAdapter output) + { + ViewModel = new OkObjectResult(output); + } + public void Success(List output) + { + ViewModel = new OkObjectResult(output); + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/ChangeProductStatusRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/ChangeProductStatusRequest.cs new file mode 100644 index 0000000..a12bfcd --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/ChangeProductStatusRequest.cs @@ -0,0 +1,16 @@ +using Core.Adapters.Lib.Inventory; +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class ChangeProductStatusRequest : Notificator, ICommand + { + public string Id { get; set; } = null!; + public ProductStatus NewStatus { get; set; } + + public bool Validate() + { + return Id != null; + } + } +} \ 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 new file mode 100644 index 0000000..fb023f2 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/CreateProductRequest.cs @@ -0,0 +1,18 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class CreateProductRequest : Notificator, ICommand + { + 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 List TagIds { get; set; } = new List(); + + public bool Validate() + { + return ProductName != null; + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsByListRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsByListRequest.cs new file mode 100644 index 0000000..a809c81 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsByListRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class GetAllProductsByListRequest : Notificator, ICommand + { + public string[] Products { get; set; } = null!; + + public bool Validate() + { + return Products != null && Products.Any(); + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsRequest.cs new file mode 100644 index 0000000..53225e2 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/GetAllProductsRequest.cs @@ -0,0 +1,12 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class GetAllProductsRequest : Notificator, ICommand + { + public bool Validate() + { + return true; + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Input/GetProductRequest.cs b/Core.Inventory.Application/UseCases/Product/Input/GetProductRequest.cs new file mode 100644 index 0000000..55ec50d --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/GetProductRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class GetProductRequest : 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 new file mode 100644 index 0000000..dffb364 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Input/UpdateProductRequest.cs @@ -0,0 +1,20 @@ +using Core.Adapters.Lib.Inventory; +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Input +{ + public class UpdateProductRequest : Notificator, ICommand + { + public string Id { get; set; } = null!; + 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 List TagIds { get; set; } = new List(); + + public bool Validate() + { + return Id != null && ProductName != null; + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs b/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs new file mode 100644 index 0000000..6ffa65d --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Ports/IProductPort.cs @@ -0,0 +1,14 @@ +using Core.Adapters.Lib.Inventory; +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Product.Ports +{ + public interface IProductPort : IBasePort, + ICommandSuccessPort, + ICommandSuccessPort>, + INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort, + INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort, + IBadRequestPort + { + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/ProductHandler.cs b/Core.Inventory.Application/UseCases/Product/ProductHandler.cs new file mode 100644 index 0000000..9cd08fa --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/ProductHandler.cs @@ -0,0 +1,214 @@ +using Core.Adapters.Lib.Inventory; +using Core.Inventory.Application.UseCases.Product.Input; +using Core.Inventory.Application.UseCases.Product.Ports; +using Core.Inventory.External.Clients; +using Core.Inventory.External.Clients.Requests; +using FluentValidation; +using Lib.Architecture.BuildingBlocks; +using Lib.Architecture.BuildingBlocks.Helpers; + +namespace Core.Inventory.Application.UseCases.Product +{ + public class ProductHandler : + 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 IInventoryServiceClient _inventoryServiceClient; + + public ProductHandler( + IProductPort port, + IValidator changeProductStatusValidator, + IValidator registerProductValidator, + IValidator updateProductValidator, + IValidator productsByListValidator, + IInventoryServiceClient inventoryDALService) + { + _port = port ?? throw new ArgumentNullException(nameof(port)); + _changeProductStatusValidator = changeProductStatusValidator ?? throw new ArgumentNullException(nameof(changeProductStatusValidator)); + _registerProductValidator = registerProductValidator ?? throw new ArgumentNullException(nameof(registerProductValidator)); + _updateProductValidator = updateProductValidator ?? throw new ArgumentNullException(nameof(updateProductValidator)); + _inventoryServiceClient = inventoryDALService ?? throw new ArgumentNullException(nameof(inventoryDALService)); + _productsByListValidator = productsByListValidator ?? throw new ArgumentNullException(nameof(productsByListValidator)); + } + + public async ValueTask ExecuteAsync(GetProductRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var result = await _inventoryServiceClient.GetProductByIdAsync(command.Id, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(GetAllProductsRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var _result = await _inventoryServiceClient.GetAllProductsAsync(cancellationToken).ConfigureAwait(false); + if (!_result.Any()) + { + _port.NoContentSuccess(); + return; + } + _port.Success(_result.ToList()); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(GetAllProductsByListRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_productsByListValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var _result = await _inventoryServiceClient.GetAllProductsByListAsync(command.Products, cancellationToken).ConfigureAwait(false); + if (!_result.Any()) + { + _port.NoContentSuccess(); + return; + } + _port.Success(_result.ToList()); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(ChangeProductStatusRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_changeProductStatusValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var result = await _inventoryServiceClient.ChangeProductStatusAsync(command.Id, command.NewStatus, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(CreateProductRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_registerProductValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var productRequest = new ProductRequest + { + TenantId = command.TenantId, + ProductName = command.ProductName, + Description = command.Description, + Status = command.Status, + TagIds = command.TagIds + }; + + var result = await _inventoryServiceClient.CreateProductAsync(productRequest, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(UpdateProductRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_updateProductValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var productAdapter = new ProductAdapter + { + Id = command.Id, + TenantId = command.TenantId, + ProductName = command.ProductName, + Description = command.Description, + Status = Enum.Parse(command.Status), + TagIds = command.TagIds.Select(id => MongoDB.Bson.ObjectId.Parse(id)).ToList() + }; + + var result = await _inventoryServiceClient.UpdateProductAsync(productAdapter, command.Id, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Validator/ChangeProductStatusValidator.cs b/Core.Inventory.Application/UseCases/Product/Validator/ChangeProductStatusValidator.cs new file mode 100644 index 0000000..2ca9249 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Validator/ChangeProductStatusValidator.cs @@ -0,0 +1,13 @@ +using Core.Inventory.Application.UseCases.Product.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Product.Validator +{ + public class ChangeProductStatusValidator : AbstractValidator + { + public ChangeProductStatusValidator() + { + 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.Application/UseCases/Product/Validator/CreateProductValidator.cs b/Core.Inventory.Application/UseCases/Product/Validator/CreateProductValidator.cs new file mode 100644 index 0000000..e158c63 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Validator/CreateProductValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Product.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Product.Validator +{ + public class CreateProductValidator : AbstractValidator + { + public CreateProductValidator() + { + RuleFor(i => i.ProductName).NotEmpty().NotNull().OverridePropertyName(x => x.ProductName).WithName("Product Name").WithMessage("Product Name is Obligatory."); + RuleFor(i => i.Description).NotEmpty().NotNull().OverridePropertyName(x => x.Description).WithName("Product Description").WithMessage("Product Description is Obligatory."); + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Validator/GetAllProductsByListValidator.cs b/Core.Inventory.Application/UseCases/Product/Validator/GetAllProductsByListValidator.cs new file mode 100644 index 0000000..d2c3e8f --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Validator/GetAllProductsByListValidator.cs @@ -0,0 +1,13 @@ +using Core.Inventory.Application.UseCases.Product.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Product.Validator +{ + public class GetAllProductsByListValidator : AbstractValidator + { + public GetAllProductsByListValidator() + { + RuleFor(i => i.Products).NotEmpty().NotNull().OverridePropertyName(x => x.Products).WithName("Products").WithMessage("Products are Obligatory."); + } + } +} \ No newline at end of file diff --git a/Core.Inventory.Application/UseCases/Product/Validator/UpdateProductValidator.cs b/Core.Inventory.Application/UseCases/Product/Validator/UpdateProductValidator.cs new file mode 100644 index 0000000..b9ab7b1 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Product/Validator/UpdateProductValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Product.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Product.Validator +{ + public class UpdateProductValidator : AbstractValidator + { + public UpdateProductValidator() + { + RuleFor(i => i.ProductName).NotEmpty().NotNull().OverridePropertyName(x => x.ProductName).WithName("Product Name").WithMessage("Product Name is Obligatory."); + RuleFor(i => i.Description).NotEmpty().NotNull().OverridePropertyName(x => x.Description).WithName("Product Description").WithMessage("Product Description 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 0fb24ea..72c254c 100644 --- a/Core.Inventory.External/Clients/IInventoryServiceClient.cs +++ b/Core.Inventory.External/Clients/IInventoryServiceClient.cs @@ -1,4 +1,5 @@ using Core.Adapters.Lib; +using Core.Adapters.Lib.Inventory; using Core.Blueprint.Mongo; using Core.Inventory.External.Clients.Adapters; using Core.Inventory.External.Clients.Requests; @@ -101,5 +102,33 @@ namespace Core.Inventory.External.Clients Task RemoveParentTagAsync([FromRoute] string tagId, [FromRoute] string parentTagId, CancellationToken cancellationToken = default); #endregion + + #region Product + + [Get("/api/v1/Product")] + Task> GetAllProductsAsync(CancellationToken cancellationToken = default); + + [Post("/api/v1/Product/GetProductList")] + Task> GetAllProductsByListAsync([FromBody] string[] request, CancellationToken cancellationToken = default); + + [Get("/api/v1/Product/{id}")] + Task GetProductByIdAsync([FromRoute] string id, CancellationToken cancellationToken = default); + + [Post("/api/v1/Product")] + Task CreateProductAsync([FromBody] ProductRequest newProduct, CancellationToken cancellationToken = default); + + [Put("/api/v1/Product/{id}")] + Task UpdateProductAsync([FromBody] ProductAdapter entity, [FromRoute] string id, CancellationToken cancellationToken = default); + + [Patch("/api/v1/Product/{id}/{newStatus}/ChangeStatus")] + Task ChangeProductStatusAsync([FromRoute] string id, [FromRoute] ProductStatus newStatus, CancellationToken cancellationToken = default); + + [Post("/api/v1/Product/{productId}/tags/{tagId}")] + Task AddTagToProductAsync([FromRoute] string productId, [FromRoute] string tagId, CancellationToken cancellationToken = default); + + [Delete("/api/v1/Product/{productId}/tags/{tagId}")] + Task RemoveTagFromProductAsync([FromRoute] string productId, [FromRoute] string tagId, CancellationToken cancellationToken = default); + + #endregion } } diff --git a/Core.Inventory.External/Clients/Requests/ProductRequest.cs b/Core.Inventory.External/Clients/Requests/ProductRequest.cs new file mode 100644 index 0000000..b170e40 --- /dev/null +++ b/Core.Inventory.External/Clients/Requests/ProductRequest.cs @@ -0,0 +1,11 @@ +namespace Core.Inventory.External.Clients.Requests +{ + public class ProductRequest + { + 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 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 a5ad0bb..011ea64 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 new file mode 100644 index 0000000..4f31a35 --- /dev/null +++ b/Core.Inventory.Service.API/Controllers/ProductController.cs @@ -0,0 +1,187 @@ +using Asp.Versioning; +using Core.Adapters.Lib.Inventory; +using Core.Inventory.Application.UseCases.Product.Input; +using Core.Inventory.Application.UseCases.Product.Ports; +using Lib.Architecture.BuildingBlocks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Core.Inventory.Service.API.Controllers +{ + /// + /// Handles all services and business rules related to . + /// + [ApiVersion("1.0")] + [Route("api/v{api-version:apiVersion}/[controller]")] + [Produces("application/json")] + [ApiController] + [AllowAnonymous] + public class ProductController : ControllerBase + { + private readonly IComponentHandler getProductHandler; + private readonly IComponentHandler getAllProductsHandler; + private readonly IComponentHandler getAllProductsByListHandler; + private readonly IComponentHandler createProductHandler; + private readonly IComponentHandler updateProductHandler; + private readonly IComponentHandler changeProductStatusHandler; + private readonly IProductPort port; + + /// + /// Handles all services and business rules related to . + /// + public ProductController( + IComponentHandler getProductHandler, + IComponentHandler getAllProductsHandler, + IComponentHandler getAllProductsByListHandler, + IComponentHandler createProductHandler, + IComponentHandler updateProductHandler, + IComponentHandler changeProductStatusHandler, + IProductPort port + ) + { + this.createProductHandler = createProductHandler; + this.updateProductHandler = updateProductHandler; + this.changeProductStatusHandler = changeProductStatusHandler; + this.getAllProductsHandler = getAllProductsHandler; + this.getProductHandler = getProductHandler; + this.getAllProductsByListHandler = getAllProductsByListHandler; + this.port = port; + } + + /// + /// Gets all the Products. + /// + [HttpGet("GetAll")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task GetAllProductsAsync(CancellationToken cancellationToken) + { + await getAllProductsHandler.ExecuteAsync(new GetAllProductsRequest { }, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Gets all the Products by Product identifiers. + /// + /// The request containing the list of Product identifiers. + /// Cancellation token for the asynchronous operation. + /// The representing the result of the service call. + /// The Products found. + /// No content if no Products are found. + /// Bad request if the Product identifiers are 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. + [HttpPost] + [Route("GetProductList")] + [ProducesResponseType(typeof(IEnumerable), 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 GetAllProductsByListAsync([FromBody] GetAllProductsByListRequest request, CancellationToken cancellationToken) + { + + if (request == null || request.Products == null || !request.Products.Any()) + { + return BadRequest("Product identifiers are required."); + } + + await getAllProductsByListHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Gets the Product by identifier. + /// + [HttpPost] + [Route("GetById")] + [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 GetProductById([FromBody] GetProductRequest request, CancellationToken cancellationToken) + { + + if (request.Id == null || !request.Id.Any()) + { + return BadRequest("Invalid Product Id"); + } + + await getProductHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Creates a new Product. + /// + [HttpPost("Create")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task CreateProductAsync([FromBody] CreateProductRequest newProduct, CancellationToken cancellationToken = default) + { + await createProductHandler.ExecuteAsync(newProduct, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Updates a full Product by identifier. + /// + [HttpPut("Update")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task UpdateProductAsync([FromBody] UpdateProductRequest request, CancellationToken cancellationToken = default) + { + await updateProductHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Changes the status of the Product. + /// + [HttpPatch] + [Route("ChangeStatus")] + [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 ChangeProductStatusAsync([FromBody] ChangeProductStatusRequest request, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.Id)) { return BadRequest("Invalid Product identifier"); } + + await changeProductStatusHandler.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 34ff62c..bfbd1b6 100644 --- a/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs +++ b/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs @@ -5,6 +5,11 @@ using Core.Inventory.Application.UseCases.Inventory.Input.Variant; using Core.Inventory.Application.UseCases.Inventory.Ports; using Core.Inventory.Application.UseCases.Inventory.Validator.Base; using Core.Inventory.Application.UseCases.Inventory.Validator.Variant; +using Core.Inventory.Application.UseCases.Product; +using Core.Inventory.Application.UseCases.Product.Adapter; +using Core.Inventory.Application.UseCases.Product.Input; +using Core.Inventory.Application.UseCases.Product.Ports; +using Core.Inventory.Application.UseCases.Product.Validator; using Core.Inventory.Application.UseCases.Tag; using Core.Inventory.Application.UseCases.Tag.Adapter; using Core.Inventory.Application.UseCases.Tag.Input; @@ -127,6 +132,30 @@ namespace Core.Inventory.Service.API.Extensions #endregion + #region Product Services + + services.AddScoped(); + services.AddScoped, ProductHandler>(); + services.AddScoped, ProductHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, GetAllProductsByListValidator>(); + services.AddScoped, ProductHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, CreateProductValidator>(); + services.AddScoped, ProductHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, UpdateProductValidator>(); + services.AddScoped, ProductHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, ChangeProductStatusValidator>(); + services.AddScoped, ProductHandler>(); + + #endregion + return services; } } diff --git a/Core.Inventory.Service.API/appsettings.Local.json b/Core.Inventory.Service.API/appsettings.Local.json index 740f657..a09b830 100644 --- a/Core.Inventory.Service.API/appsettings.Local.json +++ b/Core.Inventory.Service.API/appsettings.Local.json @@ -7,6 +7,7 @@ }, "AllowedHosts": "*", "LocalGateways": { - "InventoryDAL": "https://localhost:7037" + //"InventoryDAL": "https://localhost:7037", + "InventoryDAL": "http://portainer.white-enciso.pro:5101" } } From ab3863943d56c240eb4241085008b902c08d8e9a Mon Sep 17 00:00:00 2001 From: Efrain Marin Date: Sun, 3 Aug 2025 18:59:05 -0600 Subject: [PATCH 2/2] fix: reverted changes to local config file --- Core.Inventory.Service.API/appsettings.Local.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core.Inventory.Service.API/appsettings.Local.json b/Core.Inventory.Service.API/appsettings.Local.json index a09b830..740f657 100644 --- a/Core.Inventory.Service.API/appsettings.Local.json +++ b/Core.Inventory.Service.API/appsettings.Local.json @@ -7,7 +7,6 @@ }, "AllowedHosts": "*", "LocalGateways": { - //"InventoryDAL": "https://localhost:7037", - "InventoryDAL": "http://portainer.white-enciso.pro:5101" + "InventoryDAL": "https://localhost:7037" } }