From b9a38fbdcb4d5fa35d9e40998a5a2296d1c7bf51 Mon Sep 17 00:00:00 2001 From: Oscar Morales Date: Fri, 1 Aug 2025 11:45:37 -0600 Subject: [PATCH] Add Tag CRUD --- .../Controllers/TagController.cs | 261 ++++++++++++++++++ .../Inventory/IInventoryServiceClient.cs | 29 ++ .../Requests/Tag/AddParentTagToTagRequest.cs | 8 + .../Requests/Tag/ChangeTagStatusRequest.cs | 10 + .../Requests/Tag/CreateTagRequest.cs | 13 + .../Requests/Tag/GetAllTagByListRequest.cs | 7 + .../Requests/Tag/GetAllTagsRequest.cs | 6 + .../Inventory/Requests/Tag/GetTagRequest.cs | 7 + .../Requests/Tag/RemoveParentTagFromTag.cs | 8 + .../Requests/Tag/UpdateTagRequest.cs | 17 ++ .../Core.Inventory.External.csproj | 2 +- 11 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 Core.Inventory.BFF.API/Controllers/TagController.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/AddParentTagToTagRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/ChangeTagStatusRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/CreateTagRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagByListRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagsRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/GetTagRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/RemoveParentTagFromTag.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Tag/UpdateTagRequest.cs diff --git a/Core.Inventory.BFF.API/Controllers/TagController.cs b/Core.Inventory.BFF.API/Controllers/TagController.cs new file mode 100644 index 0000000..4a12c2e --- /dev/null +++ b/Core.Inventory.BFF.API/Controllers/TagController.cs @@ -0,0 +1,261 @@ +using Asp.Versioning; +using Core.Adapters.Lib; +using Core.Inventory.External.Clients.Inventory; +using Core.Inventory.External.Clients.Inventory.Requests.Tag; +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 Tag authentication. + /// + [ApiVersion("1.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [Consumes("application/json")] + [Produces("application/json")] + [ApiController] + [AllowAnonymous] + public class TagController(IInventoryServiceClient inventoryServiceClient, ILogger logger) : BaseController(logger) + { + /// + /// Gets all the Tags. + /// + [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 GetAllTagsService(CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllTagsService)} - Request received - Payload: "); + + return await Handle(() => inventoryServiceClient.GetAllTagsService(new GetAllTagsRequest { }, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(GetAllTagsService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload"); + throw; + } + } + + /// + /// Gets all the Tags by Tag identifiers. + /// + /// The request containing the list of Tag identifiers. + /// Cancellation token for the asynchronous operation. + /// The representing the result of the service call. + /// The Tags found. + /// No content if no Tags are found. + /// Bad request if the Tag 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 GetAllTagsByListAsync([FromBody] GetAllTagsByListRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllTagsByListAsync)} - Request received - Payload: {request}"); + + if (request == null || request.Tags == null || !request.Tags.Any()) + { + return BadRequest("Tag identifiers are required."); + } + + return await Handle(() => inventoryServiceClient.GetAllTagsByListService(request, cancellationToken)).ConfigureAwait(false); + + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetAllTagsByListAsync)} - An error occurred - {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload: {request}"); + return StatusCode(StatusCodes.Status500InternalServerError, "Internal server error"); + } + } + + + /// + /// Creates a new Tag. + /// + [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 CreateTagService(CreateTagRequest newTag, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(CreateTagService)} - Request received - Payload: {JsonSerializer.Serialize(newTag)}"); + + if (newTag == null) return BadRequest("Invalid Tag object"); + + if (string.IsNullOrEmpty(newTag.TagName)) return BadRequest("Invalid Tag name"); + + return await Handle(() => inventoryServiceClient.CreateTagService(newTag, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(CreateTagService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(newTag)}"); + throw; + } + } + + /// + /// Gets the Tag 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 GetTagByIdService(GetTagRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetTagByIdService)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.Id)) return BadRequest("Invalid Tag identifier"); + + return await Handle(() => inventoryServiceClient.GetTagByIdService(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(GetTagByIdService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Updates a full Tag 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 UpdateTagService(UpdateTagRequest newTag, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(UpdateTagService)} - Request received - Payload: {JsonSerializer.Serialize(newTag)}"); + + if (newTag == null) return BadRequest("Invalid Tag object"); + + if (string.IsNullOrEmpty(newTag.TagName)) return BadRequest("Invalid Tag name"); + + return await Handle(() => inventoryServiceClient.UpdateTagService(newTag, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(UpdateTagService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(newTag)}"); + throw; + } + } + + /// + /// Changes the status of the Tag. + /// + [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 ChangeTagStatusService([FromBody] ChangeTagStatusRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(ChangeTagStatusService)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.Id)) { return BadRequest("Invalid Tag identifier"); } + + return await Handle(() => inventoryServiceClient.ChangeTagStatusService(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(ChangeTagStatusService)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Adds a parentTag to the tag. + /// + [HttpPost] + [Route("AddParentTag")] + [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 AddParentTagAsync([FromBody] AddParentTagToTagRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(AddParentTagAsync)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + if (string.IsNullOrEmpty(request.ParentTagId)) { return BadRequest("Invalid parentTag identifier"); } + + return await Handle(() => inventoryServiceClient.AddParentTagAsync(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(AddParentTagAsync)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Remove a parentTag to the tag. + /// + [HttpDelete] + [Route("RemoveParentTag")] + [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 RemoveParentTagAsync([FromBody] RemoveParentTagFromTag request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(RemoveParentTagAsync)} - Request received - Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + if (string.IsNullOrEmpty(request.ParentTagId)) { return BadRequest("Invalid parentTag identifier"); } + + return await Handle(() => inventoryServiceClient.RemoveParentTagAsync(request, cancellationToken)).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.LogError($"{nameof(RemoveParentTagAsync)} - An Error Occurred- {ex.Message} - {ex.InnerException} - {ex.StackTrace} - with payload {JsonSerializer.Serialize(request)}"); + throw; + } + } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs index b48023f..92f8ce8 100644 --- a/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs +++ b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs @@ -1,5 +1,6 @@ using Core.Adapters.Lib; using Core.Inventory.External.Clients.Inventory.Requests.Base; +using Core.Inventory.External.Clients.Inventory.Requests.Tag; using Core.Inventory.External.Clients.Inventory.Requests.TagType; using Core.Inventory.External.Clients.Inventory.Requests.Variant; using Refit; @@ -73,5 +74,33 @@ namespace Core.Inventory.External.Clients.Inventory Task> ChangeTagTypeStatusService([Header("TrackingId")][Body] ChangeTagTypeStatusRequest request, CancellationToken cancellationToken = default); #endregion + + #region Tag + + [Post("/api/v1/Tag/Create")] + Task> CreateTagService([Header("TrackingId")][Body] CreateTagRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag/GetById")] + Task> GetTagByIdService([Header("TrackingId")][Body] GetTagRequest request, CancellationToken cancellationToken = default); + + [Get("/api/v1/Tag/GetAll")] + Task>> GetAllTagsService([Header("TrackingId")][Body] GetAllTagsRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag/GetTagList")] + Task>> GetAllTagsByListService([Header("TrackingId")][Body] GetAllTagsByListRequest request, CancellationToken cancellationToken = default); + + [Put("/api/v1/Tag/Update")] + Task> UpdateTagService([Header("TrackingId")][Body] UpdateTagRequest request, CancellationToken cancellationToken = default); + + [Patch("/api/v1/Tag/ChangeStatus")] + Task> ChangeTagStatusService([Header("TrackingId")][Body] ChangeTagStatusRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag/AddParentTag")] + Task> AddParentTagAsync([Header("TrackingId")][Body] AddParentTagToTagRequest request, CancellationToken cancellationToken = default); + + [Delete("/api/v1/Tag/RemoveParentTag")] + Task> RemoveParentTagAsync([Header("TrackingId")][Body] RemoveParentTagFromTag request, CancellationToken cancellationToken = default); + + #endregion } } diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/AddParentTagToTagRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/AddParentTagToTagRequest.cs new file mode 100644 index 0000000..a390a7b --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/AddParentTagToTagRequest.cs @@ -0,0 +1,8 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class AddParentTagToTagRequest + { + public string TagId { get; set; } + public string ParentTagId { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/ChangeTagStatusRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/ChangeTagStatusRequest.cs new file mode 100644 index 0000000..de383b6 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/ChangeTagStatusRequest.cs @@ -0,0 +1,10 @@ +using Core.Blueprint.Mongo; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class ChangeTagStatusRequest + { + public string Id { get; set; } + public StatusEnum Status { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/CreateTagRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/CreateTagRequest.cs new file mode 100644 index 0000000..1e9d97f --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/CreateTagRequest.cs @@ -0,0 +1,13 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class CreateTagRequest + { + public string TenantId { get; set; } = null!; + public string TagName { get; set; } = null!; + public string TypeId { get; set; } = null!; + public string[] ParentTagId { get; set; } = null!; + public string Slug { get; set; } = null!; + public int DisplayOrder { get; set; } + public string Icon { get; set; } = null!; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagByListRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagByListRequest.cs new file mode 100644 index 0000000..815349b --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagByListRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class GetAllTagsByListRequest + { + public string[] Tags { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagsRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagsRequest.cs new file mode 100644 index 0000000..829fe94 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetAllTagsRequest.cs @@ -0,0 +1,6 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class GetAllTagsRequest + { + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetTagRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetTagRequest.cs new file mode 100644 index 0000000..84aa67f --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/GetTagRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class GetTagRequest + { + public string Id { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/RemoveParentTagFromTag.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/RemoveParentTagFromTag.cs new file mode 100644 index 0000000..d1afd25 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/RemoveParentTagFromTag.cs @@ -0,0 +1,8 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class RemoveParentTagFromTag + { + public string TagId { get; set; } + public string ParentTagId { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Tag/UpdateTagRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Tag/UpdateTagRequest.cs new file mode 100644 index 0000000..a98c5da --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Tag/UpdateTagRequest.cs @@ -0,0 +1,17 @@ +using Core.Blueprint.Mongo; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Tag +{ + public class UpdateTagRequest + { + public string Id { get; set; } = null!; + public string TenantId { get; set; } = null!; + public string TagName { get; set; } = null!; + public string TypeId { get; set; } = null!; + public string[] ParentTagId { get; set; } = null!; + public string Slug { get; set; } = null!; + public int DisplayOrder { get; set; } + public string Icon { get; set; } = null!; + public StatusEnum Status { get; set; } + } +} diff --git a/Core.Inventory.External/Core.Inventory.External.csproj b/Core.Inventory.External/Core.Inventory.External.csproj index fdaf460..a5ad0bb 100644 --- a/Core.Inventory.External/Core.Inventory.External.csproj +++ b/Core.Inventory.External/Core.Inventory.External.csproj @@ -7,7 +7,7 @@ - +