diff --git a/Core.Inventory.BFF.API/Controllers/ProductController.cs b/Core.Inventory.BFF.API/Controllers/ProductController.cs new file mode 100644 index 0000000..693b19d --- /dev/null +++ b/Core.Inventory.BFF.API/Controllers/ProductController.cs @@ -0,0 +1,260 @@ +using Asp.Versioning; +using Core.Adapters.Lib.Inventory; +using Core.Inventory.External.Clients.Inventory; +using Core.Inventory.External.Clients.Inventory.Requests.Product; +using Lib.Architecture.BuildingBlocks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Core.Inventory.BFF.API.Controllers +{ + /// + /// Handles all requests for Product operations. + /// + [ApiVersion("1.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [Consumes("application/json")] + [Produces("application/json")] + [ApiController] + [AllowAnonymous] + public class ProductController(IInventoryServiceClient inventoryServiceClient, ILogger logger) : BaseController(logger) + { + /// + /// Gets all the Products. + /// + [HttpGet("GetAll")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task GetAllProductsService(CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllProductsService)} - Request received - Payload: "); + + return await Handle(() => inventoryServiceClient.GetAllProductsService(new GetAllProductsRequest { }, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(GetAllProductsService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload"); + throw; + } + } + + /// + /// 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. + /// Internal server error if an unexpected error occurs. + [HttpPost("GetAllByList")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task GetAllProductsByListAsync([FromBody] GetAllProductsByListRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllProductsByListAsync)} - Request received - Payload: {request}"); + + if (request == null || request.Products == null || !request.Products.Any()) + { + return BadRequest("Product identifiers are required."); + } + + return await Handle(() => inventoryServiceClient.GetAllProductsByListService(request, cancellationToken)).ConfigureAwait(false); + + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetAllProductsByListAsync)} - An error occurred - {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload: {request}"); + return StatusCode(StatusCodes.Status500InternalServerError, "Internal server error"); + } + } + + /// + /// Creates a new Product. + /// + [HttpPost("Create")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task CreateProductService(CreateProductRequest newProduct, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(CreateProductService)} - Request received - Payload: {JsonSerializer.Serialize(newProduct)}"); + + if (newProduct == null) return BadRequest("Invalid Product object"); + + if (string.IsNullOrEmpty(newProduct.ProductName)) return BadRequest("Invalid Product name"); + + return await Handle(() => inventoryServiceClient.CreateProductService(newProduct, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(CreateProductService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(newProduct)}"); + throw; + } + } + + /// + /// Gets the Product by identifier. + /// + [HttpPost("GetById")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task GetProductByIdService(GetProductRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetProductByIdService)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.Id)) return BadRequest("Invalid Product identifier"); + + return await Handle(() => inventoryServiceClient.GetProductByIdService(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(GetProductByIdService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Updates a full Product by identifier. + /// + [HttpPut("Update")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)] + [ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task UpdateProductService(UpdateProductRequest newProduct, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(UpdateProductService)} - Request received - Payload: {JsonSerializer.Serialize(newProduct)}"); + + if (newProduct == null) return BadRequest("Invalid Product object"); + + if (string.IsNullOrEmpty(newProduct.ProductName)) return BadRequest("Invalid Product name"); + + return await Handle(() => inventoryServiceClient.UpdateProductService(newProduct, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(UpdateProductService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(newProduct)}"); + throw; + } + } + + /// + /// 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 ChangeProductStatusService([FromBody] ChangeProductStatusRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(ChangeProductStatusService)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.Id)) { return BadRequest("Invalid Product identifier"); } + + return await Handle(() => inventoryServiceClient.ChangeProductStatusService(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(ChangeProductStatusService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Adds a tag to the product. + /// + [HttpPost] + [Route("AddTag")] + [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 AddTagToProductAsync([FromBody] AddTagToProductRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(AddTagToProductAsync)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.ProductId)) { return BadRequest("Invalid product identifier"); } + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + + return await Handle(() => inventoryServiceClient.AddTagToProductAsync(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(AddTagToProductAsync)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Remove a tag from the product. + /// + [HttpDelete] + [Route("RemoveTag")] + [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 RemoveTagFromProductAsync([FromBody] RemoveTagFromProductRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(RemoveTagFromProductAsync)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.ProductId)) { return BadRequest("Invalid product identifier"); } + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + + return await Handle(() => inventoryServiceClient.RemoveTagFromProductAsync(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(RemoveTagFromProductAsync)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs index 92f8ce8..77fa443 100644 --- a/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs +++ b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs @@ -1,5 +1,7 @@ using Core.Adapters.Lib; +using Core.Adapters.Lib.Inventory; using Core.Inventory.External.Clients.Inventory.Requests.Base; +using Core.Inventory.External.Clients.Inventory.Requests.Product; using Core.Inventory.External.Clients.Inventory.Requests.Tag; using Core.Inventory.External.Clients.Inventory.Requests.TagType; using Core.Inventory.External.Clients.Inventory.Requests.Variant; @@ -102,5 +104,33 @@ namespace Core.Inventory.External.Clients.Inventory Task> RemoveParentTagAsync([Header("TrackingId")][Body] RemoveParentTagFromTag request, CancellationToken cancellationToken = default); #endregion + + #region Product + + [Post("/api/v1/Product/Create")] + Task> CreateProductService([Header("TrackingId")][Body] CreateProductRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Product/GetById")] + Task> GetProductByIdService([Header("TrackingId")][Body] GetProductRequest request, CancellationToken cancellationToken = default); + + [Get("/api/v1/Product/GetAll")] + Task>> GetAllProductsService([Header("TrackingId")][Body] GetAllProductsRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Product/GetProductList")] + Task>> GetAllProductsByListService([Header("TrackingId")][Body] GetAllProductsByListRequest request, CancellationToken cancellationToken = default); + + [Put("/api/v1/Product/Update")] + Task> UpdateProductService([Header("TrackingId")][Body] UpdateProductRequest request, CancellationToken cancellationToken = default); + + [Patch("/api/v1/Product/ChangeStatus")] + Task> ChangeProductStatusService([Header("TrackingId")][Body] ChangeProductStatusRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Product/AddTag")] + Task> AddTagToProductAsync([Header("TrackingId")][Body] AddTagToProductRequest request, CancellationToken cancellationToken = default); + + [Delete("/api/v1/Product/RemoveTag")] + Task> RemoveTagFromProductAsync([Header("TrackingId")][Body] RemoveTagFromProductRequest request, CancellationToken cancellationToken = default); + + #endregion } } diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/AddTagToProductRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/AddTagToProductRequest.cs new file mode 100644 index 0000000..d0e8a69 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/AddTagToProductRequest.cs @@ -0,0 +1,8 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class AddTagToProductRequest + { + public string ProductId { get; set; } = null!; + public string TagId { get; set; } = null!; + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/ChangeProductStatusRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/ChangeProductStatusRequest.cs new file mode 100644 index 0000000..3347a24 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/ChangeProductStatusRequest.cs @@ -0,0 +1,10 @@ +using Core.Adapters.Lib.Inventory; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class ChangeProductStatusRequest + { + public string Id { get; set; } = null!; + public ProductStatus NewStatus { get; set; } + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/CreateProductRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/CreateProductRequest.cs new file mode 100644 index 0000000..8869847 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/CreateProductRequest.cs @@ -0,0 +1,11 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class CreateProductRequest + { + 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/Clients/Inventory/Requests/Product/GetAllProductsByListRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetAllProductsByListRequest.cs new file mode 100644 index 0000000..f9455d1 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetAllProductsByListRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class GetAllProductsByListRequest + { + public string[] Products { get; set; } = null!; + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/GetAllProductsRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetAllProductsRequest.cs new file mode 100644 index 0000000..ba1798f --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetAllProductsRequest.cs @@ -0,0 +1,6 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class GetAllProductsRequest + { + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/GetProductRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetProductRequest.cs new file mode 100644 index 0000000..172470f --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/GetProductRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class GetProductRequest + { + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/RemoveTagFromProductRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/RemoveTagFromProductRequest.cs new file mode 100644 index 0000000..79089dd --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/RemoveTagFromProductRequest.cs @@ -0,0 +1,8 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class RemoveTagFromProductRequest + { + public string ProductId { get; set; } = null!; + public string TagId { get; set; } = null!; + } +} \ No newline at end of file diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Product/UpdateProductRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Product/UpdateProductRequest.cs new file mode 100644 index 0000000..c0dfd40 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Product/UpdateProductRequest.cs @@ -0,0 +1,14 @@ +using Core.Adapters.Lib.Inventory; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Product +{ + public class UpdateProductRequest + { + 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(); + } +} \ 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 @@ - +