From 6b0a68194277aa614d5158e3dd6a82dffc7cb4bc Mon Sep 17 00:00:00 2001 From: Oscar Morales Date: Fri, 1 Aug 2025 11:45:22 -0600 Subject: [PATCH] Add Tag CRUD --- .../UseCases/Tag/Adapter/TagPort.cs | 19 ++ .../Tag/Input/AddParentTagToTagRequest.cs | 14 + .../Tag/Input/ChangeTagStatusRequest.cs | 16 ++ .../UseCases/Tag/Input/CreateTagRequest.cs | 20 ++ .../Tag/Input/GetAllTagByListRequest.cs | 14 + .../UseCases/Tag/Input/GetAllTagRequest.cs | 12 + .../UseCases/Tag/Input/GetTagRequest.cs | 13 + .../Input/RemoveParentTagFromTagRequest.cs | 14 + .../UseCases/Tag/Input/UpdateTagRequest.cs | 20 ++ .../UseCases/Tag/Ports/ITagPort.cs | 14 + .../UseCases/Tag/TagHandler.cs | 266 ++++++++++++++++++ .../Tag/Validator/ChangeTagStatusValidator.cs | 14 + .../Tag/Validator/CreateTagValidator.cs | 14 + .../Tag/Validator/GetAllTagByListValidator.cs | 14 + .../Tag/Validator/UpdateTagValidator.cs | 14 + .../Clients/IInventoryServiceClient.cs | 28 ++ .../Clients/Requests/TagRequest.cs | 13 + .../Core.Inventory.External.csproj | 2 +- .../Controllers/TagController.cs | 239 ++++++++++++++++ .../Extensions/ServiceCollectionExtension.cs | 32 +++ 20 files changed, 791 insertions(+), 1 deletion(-) create mode 100644 Core.Inventory.Application/UseCases/Tag/Adapter/TagPort.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/AddParentTagToTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/ChangeTagStatusRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/CreateTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/GetAllTagByListRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/GetAllTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/GetTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/RemoveParentTagFromTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Input/UpdateTagRequest.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Ports/ITagPort.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/TagHandler.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Validator/ChangeTagStatusValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Validator/CreateTagValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Validator/GetAllTagByListValidator.cs create mode 100644 Core.Inventory.Application/UseCases/Tag/Validator/UpdateTagValidator.cs create mode 100644 Core.Inventory.External/Clients/Requests/TagRequest.cs create mode 100644 Core.Inventory.Service.API/Controllers/TagController.cs diff --git a/Core.Inventory.Application/UseCases/Tag/Adapter/TagPort.cs b/Core.Inventory.Application/UseCases/Tag/Adapter/TagPort.cs new file mode 100644 index 0000000..7c6065c --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Adapter/TagPort.cs @@ -0,0 +1,19 @@ +using Core.Adapters.Lib; +using Core.Inventory.Application.UseCases.Tag.Ports; +using Lib.Architecture.BuildingBlocks; +using Microsoft.AspNetCore.Mvc; + +namespace Core.Inventory.Application.UseCases.Tag.Adapter +{ + public class TagPort : BasePresenter, ITagPort + { + public void Success(TagAdapter output) + { + ViewModel = new OkObjectResult(output); + } + public void Success(List output) + { + ViewModel = new OkObjectResult(output); + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/AddParentTagToTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/AddParentTagToTagRequest.cs new file mode 100644 index 0000000..5e9a299 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/AddParentTagToTagRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class AddParentTagToTagRequest : Notificator, ICommand + { + public string TagId { get; set; } + public string ParentTagId { get; set; } + public bool Validate() + { + return true; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/ChangeTagStatusRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/ChangeTagStatusRequest.cs new file mode 100644 index 0000000..c359142 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/ChangeTagStatusRequest.cs @@ -0,0 +1,16 @@ +using Core.Blueprint.Mongo; +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class ChangeTagStatusRequest : Notificator, ICommand + { + public string Id { get; set; } + public StatusEnum Status { get; set; } + + public bool Validate() + { + return Id != null; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/CreateTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/CreateTagRequest.cs new file mode 100644 index 0000000..d8acef4 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/CreateTagRequest.cs @@ -0,0 +1,20 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class CreateTagRequest : Notificator, ICommand + { + 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 bool Validate() + { + return TagName != null; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagByListRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagByListRequest.cs new file mode 100644 index 0000000..4909c0a --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagByListRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class GetAllTagsByListRequest : Notificator, ICommand + { + public string[] Tags { get; set; } + + public bool Validate() + { + return Tags != null; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagRequest.cs new file mode 100644 index 0000000..9f76060 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/GetAllTagRequest.cs @@ -0,0 +1,12 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class GetAllTagsRequest : ICommand + { + public bool Validate() + { + return true; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/GetTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/GetTagRequest.cs new file mode 100644 index 0000000..6ba374e --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/GetTagRequest.cs @@ -0,0 +1,13 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class GetTagRequest : Notificator, ICommand + { + public string Id { get; set; } + public bool Validate() + { + return Id != null; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/RemoveParentTagFromTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/RemoveParentTagFromTagRequest.cs new file mode 100644 index 0000000..fe98eaa --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/RemoveParentTagFromTagRequest.cs @@ -0,0 +1,14 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class RemoveParentTagFromTagRequest : Notificator, ICommand + { + public string TagId { get; set; } + public string ParentTagId { get; set; } + public bool Validate() + { + return true; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Input/UpdateTagRequest.cs b/Core.Inventory.Application/UseCases/Tag/Input/UpdateTagRequest.cs new file mode 100644 index 0000000..95e08ca --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Input/UpdateTagRequest.cs @@ -0,0 +1,20 @@ +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Input +{ + public class UpdateTagRequest : Notificator, ICommand + { + 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 bool Validate() + { + return Id != null; + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Ports/ITagPort.cs b/Core.Inventory.Application/UseCases/Tag/Ports/ITagPort.cs new file mode 100644 index 0000000..e23d517 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Ports/ITagPort.cs @@ -0,0 +1,14 @@ +using Core.Adapters.Lib; +using Lib.Architecture.BuildingBlocks; + +namespace Core.Inventory.Application.UseCases.Tag.Ports +{ + public interface ITagPort : IBasePort, + ICommandSuccessPort, + ICommandSuccessPort>, + INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort, + INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort, + IBadRequestPort + { + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/TagHandler.cs b/Core.Inventory.Application/UseCases/Tag/TagHandler.cs new file mode 100644 index 0000000..f1b02b3 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/TagHandler.cs @@ -0,0 +1,266 @@ +using Core.Adapters.Lib; +using Core.Inventory.Application.UseCases.Tag.Input; +using Core.Inventory.Application.UseCases.Tag.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.Tag +{ + public class TagHandler : + IComponentHandler, + IComponentHandler, + IComponentHandler, + IComponentHandler, + IComponentHandler, + IComponentHandler, + IComponentHandler, + IComponentHandler + { + private readonly ITagPort _port; + private readonly IValidator _changeTagStatusValidator; + private readonly IValidator _registerTagValidator; + private readonly IValidator _updateTagValidator; + private readonly IValidator _TagsByListValidator; + private readonly IInventoryServiceClient _inventoryServiceClient; + + public TagHandler( + ITagPort port, + IValidator changeTagStatusValidator, + IValidator registerTagValidator, + IValidator updateTagValidator, + IValidator TagsByListValidator, + IInventoryServiceClient inventoryDALService) + { + _port = port ?? throw new ArgumentNullException(nameof(port)); + _changeTagStatusValidator = changeTagStatusValidator ?? throw new ArgumentNullException(nameof(changeTagStatusValidator)); + _registerTagValidator = registerTagValidator ?? throw new ArgumentNullException(nameof(registerTagValidator)); + _updateTagValidator = updateTagValidator ?? throw new ArgumentNullException(nameof(updateTagValidator)); + _inventoryServiceClient = inventoryDALService ?? throw new ArgumentNullException(nameof(inventoryDALService)); + _TagsByListValidator = TagsByListValidator ?? throw new ArgumentNullException(nameof(TagsByListValidator)); + } + + public async ValueTask ExecuteAsync(GetTagRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var result = await _inventoryServiceClient.GetTagByIdAsync(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(GetAllTagsRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var _result = await _inventoryServiceClient.GetAllTagsAsync().ConfigureAwait(false); + if (!_result.Any()) + { + _port.NoContentSuccess(); + return; + } + _port.Success(_result.ToList()); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(GetAllTagsByListRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_TagsByListValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var _result = await _inventoryServiceClient.GetAllTagsByListAsync(command.Tags, cancellationToken).ConfigureAwait(false); + if (!_result.Any()) + { + _port.NoContentSuccess(); + return; + } + _port.Success(_result.ToList()); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(ChangeTagStatusRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_changeTagStatusValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var result = await _inventoryServiceClient.ChangeStatusTagAsync(command.Id, command.Status, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(CreateTagRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_registerTagValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var request = new TagRequest + { + TenantId = command.TenantId, + TagName = command.TagName, + TypeId = command.TypeId, + ParentTagId = command.ParentTagId, + Slug = command.Slug, + DisplayOrder = command.DisplayOrder, + Icon = command.Icon, + }; + + var result = await _inventoryServiceClient.CreateTagAsync(request, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(UpdateTagRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + if (!command.IsValid(_updateTagValidator)) + { + _port.ValidationErrors(command.Notifications); + return; + } + + var request = new TagAdapter + { + Id = command.Id, + TenantId = command.TenantId, + TagName = command.TagName, + TypeId = command.TypeId, + ParentTagId = command.ParentTagId, + Slug = command.Slug, + DisplayOrder = command.DisplayOrder, + Icon = command.Icon + }; + + string id = command.Id; + + var result = await _inventoryServiceClient.UpdateTagAsync(request, id, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(AddParentTagToTagRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var result = await _inventoryServiceClient.AddParentTagAsync(command.TagId, command.ParentTagId, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + + public async ValueTask ExecuteAsync(RemoveParentTagFromTagRequest command, CancellationToken cancellationToken = default) + { + try + { + ArgumentNullException.ThrowIfNull(command); + + var result = await _inventoryServiceClient.RemoveParentTagAsync(command.TagId, command.ParentTagId, cancellationToken).ConfigureAwait(false); + + if (result == null) + { + _port.NoContentSuccess(); + return; + } + + _port.Success(result); + } + catch (Exception ex) + { + ApiResponseHelper.EvaluatePort(ex, _port); + } + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Validator/ChangeTagStatusValidator.cs b/Core.Inventory.Application/UseCases/Tag/Validator/ChangeTagStatusValidator.cs new file mode 100644 index 0000000..d0a4886 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Validator/ChangeTagStatusValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Tag.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Tag.Validator +{ + public class ChangeTagStatusValidator : AbstractValidator + { + public ChangeTagStatusValidator() + { + RuleFor(i => i.Id).NotEmpty().NotNull().OverridePropertyName(x => x.Id).WithName("Tag ID").WithMessage("Tag ID is Obligatory."); + RuleFor(i => i.Status).NotNull().OverridePropertyName(x => x.Status).WithName("Status").WithMessage("Status is Obligatory."); + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Validator/CreateTagValidator.cs b/Core.Inventory.Application/UseCases/Tag/Validator/CreateTagValidator.cs new file mode 100644 index 0000000..d22c529 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Validator/CreateTagValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Tag.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Tag.Validator +{ + public class CreateTagValidator : AbstractValidator + { + public CreateTagValidator() + { + RuleFor(i => i.TagName).NotEmpty().NotNull().OverridePropertyName(x => x.TagName).WithName("Tag Name").WithMessage("Tag Name is Obligatory."); + RuleFor(i => i.TypeId).NotEmpty().NotNull().OverridePropertyName(x => x.TypeId).WithName("Tag TypeId").WithMessage("Tag TypeId is Obligatory."); + } + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Validator/GetAllTagByListValidator.cs b/Core.Inventory.Application/UseCases/Tag/Validator/GetAllTagByListValidator.cs new file mode 100644 index 0000000..751420b --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Validator/GetAllTagByListValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Tag.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Tag.Validator +{ + public class GetAllTagsByListValidator : AbstractValidator + { + public GetAllTagsByListValidator() + { + RuleFor(i => i.Tags).NotEmpty().NotNull().OverridePropertyName(x => x.Tags).WithName("Tags").WithMessage("Tags are Obligatory."); + } + + } +} diff --git a/Core.Inventory.Application/UseCases/Tag/Validator/UpdateTagValidator.cs b/Core.Inventory.Application/UseCases/Tag/Validator/UpdateTagValidator.cs new file mode 100644 index 0000000..a530898 --- /dev/null +++ b/Core.Inventory.Application/UseCases/Tag/Validator/UpdateTagValidator.cs @@ -0,0 +1,14 @@ +using Core.Inventory.Application.UseCases.Tag.Input; +using FluentValidation; + +namespace Core.Inventory.Application.UseCases.Tag.Validator +{ + public class UpdateTagValidator : AbstractValidator + { + public UpdateTagValidator() + { + RuleFor(i => i.TagName).NotEmpty().NotNull().OverridePropertyName(x => x.TagName).WithName("Tag Name").WithMessage("Tag Name is Obligatory."); + RuleFor(i => i.TypeId).NotEmpty().NotNull().OverridePropertyName(x => x.TypeId).WithName("Tag TypeId").WithMessage("Tag TypeId is Obligatory."); + } + } +} diff --git a/Core.Inventory.External/Clients/IInventoryServiceClient.cs b/Core.Inventory.External/Clients/IInventoryServiceClient.cs index d7bf872..0fb24ea 100644 --- a/Core.Inventory.External/Clients/IInventoryServiceClient.cs +++ b/Core.Inventory.External/Clients/IInventoryServiceClient.cs @@ -73,5 +73,33 @@ namespace Core.Inventory.External.Clients Task ChangeStatusTagTypeAsync([FromRoute] string id, [FromRoute] StatusEnum newStatus, CancellationToken cancellationToken = default); #endregion + + #region Tag + + [Get("/api/v1/Tag")] + Task> GetAllTagsAsync(CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag/GetTagList")] + Task> GetAllTagsByListAsync([FromBody] string[] request, CancellationToken cancellationToken = default); + + [Get("/api/v1/Tag/{id}")] + Task GetTagByIdAsync([FromRoute] string id, CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag")] + Task CreateTagAsync([FromBody] TagRequest newTag, CancellationToken cancellationToken = default); + + [Put("/api/v1/Tag/{id}")] + Task UpdateTagAsync([FromBody] TagAdapter entity, [FromRoute] string id, CancellationToken cancellationToken = default); + + [Patch("/api/v1/Tag/{id}/{newStatus}/ChangeStatus")] + Task ChangeStatusTagAsync([FromRoute] string id, [FromRoute] StatusEnum newStatus, CancellationToken cancellationToken = default); + + [Post("/api/v1/Tag/{tagId}/ParentTags/{parentTagId}/Add")] + Task AddParentTagAsync([FromRoute] string tagId, [FromRoute] string parentTagId, CancellationToken cancellationToken = default); + + [Delete("/api/v1/Tag/{tagId}/ParentTags/{parentTagId}/Remove")] + Task RemoveParentTagAsync([FromRoute] string tagId, [FromRoute] string parentTagId, CancellationToken cancellationToken = default); + + #endregion } } diff --git a/Core.Inventory.External/Clients/Requests/TagRequest.cs b/Core.Inventory.External/Clients/Requests/TagRequest.cs new file mode 100644 index 0000000..c334283 --- /dev/null +++ b/Core.Inventory.External/Clients/Requests/TagRequest.cs @@ -0,0 +1,13 @@ +namespace Core.Inventory.External.Clients.Requests +{ + public class TagRequest + { + 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/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 @@ - + diff --git a/Core.Inventory.Service.API/Controllers/TagController.cs b/Core.Inventory.Service.API/Controllers/TagController.cs new file mode 100644 index 0000000..88b21d5 --- /dev/null +++ b/Core.Inventory.Service.API/Controllers/TagController.cs @@ -0,0 +1,239 @@ +using Asp.Versioning; +using Core.Adapters.Lib; +using Core.Inventory.Application.UseCases.Tag.Input; +using Core.Inventory.Application.UseCases.Tag.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 TagController : ControllerBase + { + private readonly IComponentHandler getTagHandler; + private readonly IComponentHandler getAllTagsHandler; + private readonly IComponentHandler getAllTagsByListHandler; + private readonly IComponentHandler createTagHandler; + private readonly IComponentHandler updateTagHandler; + private readonly IComponentHandler changeTagStatusHandler; + private readonly IComponentHandler addParentTagToTagHandler; + private readonly IComponentHandler removeParentTagFromTagUserHandler; + private readonly ITagPort port; + + /// + /// Handles all services and business rules related to . + /// + public TagController( + IComponentHandler getTagHandler, + IComponentHandler getAllTagsHandler, + IComponentHandler getAllTagsByListHandler, + IComponentHandler createTagHandler, + IComponentHandler updateTagHandler, + IComponentHandler changeTagStatusHandler, + IComponentHandler addParentTagToTagHandler, + IComponentHandler removeParentTagFromTagUserHandler, + ITagPort port + ) + { + this.createTagHandler = createTagHandler; + this.updateTagHandler = updateTagHandler; + this.changeTagStatusHandler = changeTagStatusHandler; + this.getAllTagsHandler = getAllTagsHandler; + this.getTagHandler = getTagHandler; + this.getAllTagsByListHandler = getAllTagsByListHandler; + this.addParentTagToTagHandler = addParentTagToTagHandler; + this.removeParentTagFromTagUserHandler = removeParentTagFromTagUserHandler; + this.port = port; + } + + /// + /// Gets all the Tags. + /// + [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 GetAllTagsAsync(CancellationToken cancellationToken) + { + await getAllTagsHandler.ExecuteAsync(new GetAllTagsRequest { }, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// 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. + /// 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("GetTagList")] + [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 GetAllTagsByListAsync([FromBody] GetAllTagsByListRequest request, CancellationToken cancellationToken) + { + + if (request == null || request.Tags == null || !request.Tags.Any()) + { + return BadRequest("Tag identifiers are required."); + } + + await getAllTagsByListHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Gets the Tag 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 GetTagById([FromBody] GetTagRequest request, CancellationToken cancellationToken) + { + + if (request.Id == null || !request.Id.Any()) + { + return BadRequest("Invalid Tag Id"); + } + + await getTagHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Creates a new Tag. + /// + [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 CreateTagAsync([FromBody] CreateTagRequest newTag, CancellationToken cancellationToken = default) + { + await createTagHandler.ExecuteAsync(newTag, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// Updates a full Tag 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 UpdateTagAsync([FromBody] UpdateTagRequest request, CancellationToken cancellationToken = default) + { + await updateTagHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// 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 ChangeTagStatusAsync([FromBody] ChangeTagStatusRequest request, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.Id)) { return BadRequest("Invalid Tag identifier"); } + + await changeTagStatusHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); + + return port.ViewModel; + } + + /// + /// 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) + { + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + if (string.IsNullOrEmpty(request.ParentTagId)) { return BadRequest("Invalid parentTag identifier"); } + + await addParentTagToTagHandler.ExecuteAsync(request, cancellationToken); + + return port.ViewModel; + } + + /// + /// 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] RemoveParentTagFromTagRequest request, + CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(request.TagId)) { return BadRequest("Invalid tag identifier"); } + if (string.IsNullOrEmpty(request.ParentTagId)) { return BadRequest("Invalid parentTag identifier"); } + + await removeParentTagFromTagUserHandler.ExecuteAsync(request, cancellationToken); + + return port.ViewModel; + } + } +} diff --git a/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs b/Core.Inventory.Service.API/Extensions/ServiceCollectionExtension.cs index ea386b4..34ff62c 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.Tag; +using Core.Inventory.Application.UseCases.Tag.Adapter; +using Core.Inventory.Application.UseCases.Tag.Input; +using Core.Inventory.Application.UseCases.Tag.Ports; +using Core.Inventory.Application.UseCases.Tag.Validator; using Core.Inventory.Application.UseCases.TagType; using Core.Inventory.Application.UseCases.TagType.Adapter; using Core.Inventory.Application.UseCases.TagType.Input; @@ -95,6 +100,33 @@ namespace Core.Inventory.Service.API.Extensions #endregion + #region Tag Services + + services.AddScoped(); + services.AddScoped, TagHandler>(); + services.AddScoped, TagHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, GetAllTagsByListValidator>(); + services.AddScoped, TagHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, CreateTagValidator>(); + services.AddScoped, TagHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, UpdateTagValidator>(); + services.AddScoped, TagHandler>(); + + services.AddValidatorsFromAssemblyContaining(); + services.AddScoped, ChangeTagStatusValidator>(); + services.AddScoped, TagHandler>(); + + services.AddScoped, TagHandler>(); + services.AddScoped, TagHandler>(); + + #endregion + return services; } }