feat: Added Product controller and endpoints (Service Layer)

This commit is contained in:
2025-08-03 18:56:47 -06:00
parent fc093e8bff
commit c446fa652e
19 changed files with 654 additions and 2 deletions

View File

@@ -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<ProductAdapter> output)
{
ViewModel = new OkObjectResult(output);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<string> TagIds { get; set; } = new List<string>();
public bool Validate()
{
return ProductName != null;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<string> TagIds { get; set; } = new List<string>();
public bool Validate()
{
return Id != null && ProductName != null;
}
}
}

View File

@@ -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<ProductAdapter>,
ICommandSuccessPort<List<ProductAdapter>>,
INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort,
INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort,
IBadRequestPort
{
}
}

View File

@@ -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<ChangeProductStatusRequest>,
IComponentHandler<GetAllProductsRequest>,
IComponentHandler<GetAllProductsByListRequest>,
IComponentHandler<UpdateProductRequest>,
IComponentHandler<GetProductRequest>,
IComponentHandler<CreateProductRequest>
{
private readonly IProductPort _port;
private readonly IValidator<ChangeProductStatusRequest> _changeProductStatusValidator;
private readonly IValidator<CreateProductRequest> _registerProductValidator;
private readonly IValidator<UpdateProductRequest> _updateProductValidator;
private readonly IValidator<GetAllProductsByListRequest> _productsByListValidator;
private readonly IInventoryServiceClient _inventoryServiceClient;
public ProductHandler(
IProductPort port,
IValidator<ChangeProductStatusRequest> changeProductStatusValidator,
IValidator<CreateProductRequest> registerProductValidator,
IValidator<UpdateProductRequest> updateProductValidator,
IValidator<GetAllProductsByListRequest> 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<ProductStatus>(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);
}
}
}
}

View File

@@ -0,0 +1,13 @@
using Core.Inventory.Application.UseCases.Product.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Product.Validator
{
public class ChangeProductStatusValidator : AbstractValidator<ChangeProductStatusRequest>
{
public ChangeProductStatusValidator()
{
RuleFor(i => i.Id).NotEmpty().NotNull().OverridePropertyName(x => x.Id).WithName("Product Id").WithMessage("Product Id is Obligatory.");
}
}
}

View File

@@ -0,0 +1,14 @@
using Core.Inventory.Application.UseCases.Product.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Product.Validator
{
public class CreateProductValidator : AbstractValidator<CreateProductRequest>
{
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.");
}
}
}

View File

@@ -0,0 +1,13 @@
using Core.Inventory.Application.UseCases.Product.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Product.Validator
{
public class GetAllProductsByListValidator : AbstractValidator<GetAllProductsByListRequest>
{
public GetAllProductsByListValidator()
{
RuleFor(i => i.Products).NotEmpty().NotNull().OverridePropertyName(x => x.Products).WithName("Products").WithMessage("Products are Obligatory.");
}
}
}

View File

@@ -0,0 +1,14 @@
using Core.Inventory.Application.UseCases.Product.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Product.Validator
{
public class UpdateProductValidator : AbstractValidator<UpdateProductRequest>
{
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.");
}
}
}