From bb7e07a75989dfd95d5b8ec153fa7dd92e957195 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Thu, 27 Nov 2025 11:44:52 +0500 Subject: [PATCH 1/5] feat: new proposal for api reorganization --- .../Items => Controllers}/ItemsController.cs | 16 +++-- Api/DependencyInjection.cs | 5 +- Api/ExternalDeps/EmployeesApi/EmployeesApi.cs | 2 +- .../Items/CreateItem/CreateItemHandler.cs | 34 --------- .../Items/GetAllItems/HolderEmployeeMapper.cs | 22 ------ Api/Responses/ItemTypesResponse.cs | 4 +- Application/Commands/CreateItemCommand.cs | 69 ------------------- .../EmployeesApi}/EmployeesResponse.cs | 2 +- .../Features/Dtos}/ItemTypeDto.cs | 2 +- .../Items/CreateItem/CreateItemHandler.cs | 54 +++++++++++++++ .../Items/CreateItem/CreateItemRequest.cs | 2 +- .../Items/CreateItem/CreateItemResponse.cs | 2 +- .../Items/GetAllItems/GetAllItemsHandler.cs | 28 +++++--- .../GetAllItems/GetAllItemsHandlerTests.cs | 0 .../Items/GetAllItems/GetAllItemsResponse.cs | 4 +- 15 files changed, 92 insertions(+), 154 deletions(-) rename Api/{Features/Items => Controllers}/ItemsController.cs (74%) delete mode 100644 Api/Features/Items/CreateItem/CreateItemHandler.cs delete mode 100644 Api/Features/Items/GetAllItems/HolderEmployeeMapper.cs delete mode 100644 Application/Commands/CreateItemCommand.cs rename {Api/ExternalDeps/EmployeesApi/Responses => Application/ExternalDeps/EmployeesApi}/EmployeesResponse.cs (86%) rename {Api/Responses => Application/Features/Dtos}/ItemTypeDto.cs (74%) create mode 100644 Application/Features/Items/CreateItem/CreateItemHandler.cs rename {Api => Application}/Features/Items/CreateItem/CreateItemRequest.cs (91%) rename {Api => Application}/Features/Items/CreateItem/CreateItemResponse.cs (63%) rename {Api => Application}/Features/Items/GetAllItems/GetAllItemsHandler.cs (51%) rename Api/Features/Items/GetAllItems/HolderEmployeeMapperTests.cs => Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs (100%) rename {Api => Application}/Features/Items/GetAllItems/GetAllItemsResponse.cs (90%) diff --git a/Api/Features/Items/ItemsController.cs b/Api/Controllers/ItemsController.cs similarity index 74% rename from Api/Features/Items/ItemsController.cs rename to Api/Controllers/ItemsController.cs index df2235c..e83ee1d 100644 --- a/Api/Features/Items/ItemsController.cs +++ b/Api/Controllers/ItemsController.cs @@ -1,12 +1,13 @@ using System.ComponentModel.DataAnnotations; -using Api.Features.Items.CreateItem; -using Api.Features.Items.GetAllItems; +using Api.ExternalDeps.EmployeesApi; using Application.Commands; +using Application.Features.Items.CreateItem; +using Application.Features.Items.GetAllItems; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Filters; -namespace Api.Features.Items; +namespace Api.Controllers; [Authorize] [ApiController] @@ -18,11 +19,14 @@ public class ItemsController : ControllerBase /// [RequiresPermission(UserClaimsProvider.CanViewItems)] [HttpGet] - public Task GetAllItemsAsync( - [FromServices] GetAllItemsHandler getAllItemsHandler + public async Task GetAllItemsAsync( + [FromServices] GetAllItemsHandler getAllItemsHandler, + [FromServices] EmployeesApi employeesApi ) { - return getAllItemsHandler.HandleAsync(); + var allEmployeesResponse = await employeesApi.GetAllEmployeesAsync(); + + return await getAllItemsHandler.HandleAsync(allEmployeesResponse); } /// diff --git a/Api/DependencyInjection.cs b/Api/DependencyInjection.cs index ecd0766..9509072 100644 --- a/Api/DependencyInjection.cs +++ b/Api/DependencyInjection.cs @@ -1,8 +1,8 @@ using Api.ExternalDeps.EmployeesApi; -using Api.Features.Items.CreateItem; -using Api.Features.Items.GetAllItems; using Application; using Application.Commands; +using Application.Features.Items.CreateItem; +using Application.Features.Items.GetAllItems; using Application.Queries; using Microsoft.EntityFrameworkCore; @@ -35,7 +35,6 @@ public static void AddApplication(this IServiceCollection services, IConfigurati services.AddTransient(); services.AddTransient(); - services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Api/ExternalDeps/EmployeesApi/EmployeesApi.cs b/Api/ExternalDeps/EmployeesApi/EmployeesApi.cs index ad8b306..65d2efe 100644 --- a/Api/ExternalDeps/EmployeesApi/EmployeesApi.cs +++ b/Api/ExternalDeps/EmployeesApi/EmployeesApi.cs @@ -1,4 +1,4 @@ -using Api.ExternalDeps.EmployeesApi.Responses; +using Application.ExternalDeps.EmployeesApi; using Microsoft.Extensions.Options; using TourmalineCore.AspNetCore.JwtAuthentication.Core.Options; diff --git a/Api/Features/Items/CreateItem/CreateItemHandler.cs b/Api/Features/Items/CreateItem/CreateItemHandler.cs deleted file mode 100644 index 6246b54..0000000 --- a/Api/Features/Items/CreateItem/CreateItemHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Application.Commands; - -namespace Api.Features.Items.CreateItem; - -public class CreateItemHandler -{ - private readonly CreateItemCommand _createItemCommand; - - public CreateItemHandler(CreateItemCommand createItemCommand) - { - _createItemCommand = createItemCommand; - } - - public async Task HandleAsync(CreateItemRequest createItemRequest) - { - var createItemCommandParams = new CreateItemCommandParams - { - Name = createItemRequest.Name, - SerialNumber = createItemRequest.SerialNumber, - ItemTypeId = createItemRequest.ItemTypeId, - Price = createItemRequest.Price, - Description = createItemRequest.Description, - PurchaseDate = createItemRequest.PurchaseDate, - HolderEmployeeId = createItemRequest.HolderEmployeeId - }; - - var newItemId = await _createItemCommand.ExecuteAsync(createItemCommandParams); - - return new CreateItemResponse() - { - NewItemId = newItemId - }; - } -} diff --git a/Api/Features/Items/GetAllItems/HolderEmployeeMapper.cs b/Api/Features/Items/GetAllItems/HolderEmployeeMapper.cs deleted file mode 100644 index 0c3eb12..0000000 --- a/Api/Features/Items/GetAllItems/HolderEmployeeMapper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Api.ExternalDeps.EmployeesApi.Responses; - -namespace Api.Features.Items.GetAllItems; - -public class HolderEmployeeMapper -{ - public const string NotFoundEmployeeFullName = "Not Found"; - - public static EmployeeDto? MapToEmployeeDto(long? holderEmployeeId, EmployeesResponse employeesResponse) - { - return holderEmployeeId == null - ? null - : new EmployeeDto - { - Id = holderEmployeeId.Value, - FullName = employeesResponse - .Employees - .SingleOrDefault(y => y.Id == holderEmployeeId.Value) - ?.FullName ?? NotFoundEmployeeFullName - }; - } -} diff --git a/Api/Responses/ItemTypesResponse.cs b/Api/Responses/ItemTypesResponse.cs index e4389bf..da88bdd 100644 --- a/Api/Responses/ItemTypesResponse.cs +++ b/Api/Responses/ItemTypesResponse.cs @@ -1,4 +1,6 @@ -namespace Api.Responses; +using Application.Features.Dtos; + +namespace Api.Responses; public class ItemTypesResponse { diff --git a/Application/Commands/CreateItemCommand.cs b/Application/Commands/CreateItemCommand.cs deleted file mode 100644 index 91ba52e..0000000 --- a/Application/Commands/CreateItemCommand.cs +++ /dev/null @@ -1,69 +0,0 @@ - -using Core.Entities; -using Microsoft.EntityFrameworkCore; - -namespace Application.Commands; - -public class CreateItemCommandParams -{ - public required string Name { get; set; } - - public required string SerialNumber { get; set; } - - public required long ItemTypeId { get; set; } - - public required decimal Price { get; set; } - - public required string Description { get; set; } - - public required DateOnly? PurchaseDate { get; set; } - - public required long? HolderEmployeeId { get; set; } -} - -public class CreateItemCommand -{ - private readonly TenantAppDbContext _context; - private readonly IClaimsProvider _claimsProvider; - - public CreateItemCommand( - TenantAppDbContext context, - IClaimsProvider claimsProvider - ) - { - _context = context; - _claimsProvider = claimsProvider; - } - - public async Task ExecuteAsync(CreateItemCommandParams createItemCommandParams) - { - var itemTypeIdDoesNotExistWithinTenant = await _context - .QueryableWithinTenant() - .AllAsync(x => x.Id != createItemCommandParams.ItemTypeId); - - if (itemTypeIdDoesNotExistWithinTenant) - { - throw new Exception($"Passed item type where id={createItemCommandParams.ItemTypeId} is not found within tenant where id={_claimsProvider.TenantId}"); - } - - var item = new Item - { - TenantId = _claimsProvider.TenantId, - Name = createItemCommandParams.Name, - SerialNumber = createItemCommandParams.SerialNumber, - ItemTypeId = createItemCommandParams.ItemTypeId, - Price = createItemCommandParams.Price, - Description = createItemCommandParams.Description, - PurchaseDate = createItemCommandParams.PurchaseDate, - HolderEmployeeId = createItemCommandParams.HolderEmployeeId - }; - - await _context - .Items - .AddAsync(item); - - await _context.SaveChangesAsync(); - - return item.Id; - } -} diff --git a/Api/ExternalDeps/EmployeesApi/Responses/EmployeesResponse.cs b/Application/ExternalDeps/EmployeesApi/EmployeesResponse.cs similarity index 86% rename from Api/ExternalDeps/EmployeesApi/Responses/EmployeesResponse.cs rename to Application/ExternalDeps/EmployeesApi/EmployeesResponse.cs index 411123a..fe9b1e8 100644 --- a/Api/ExternalDeps/EmployeesApi/Responses/EmployeesResponse.cs +++ b/Application/ExternalDeps/EmployeesApi/EmployeesResponse.cs @@ -1,4 +1,4 @@ -namespace Api.ExternalDeps.EmployeesApi.Responses; +namespace Application.ExternalDeps.EmployeesApi; public class EmployeesResponse { diff --git a/Api/Responses/ItemTypeDto.cs b/Application/Features/Dtos/ItemTypeDto.cs similarity index 74% rename from Api/Responses/ItemTypeDto.cs rename to Application/Features/Dtos/ItemTypeDto.cs index e5729e4..661277c 100644 --- a/Api/Responses/ItemTypeDto.cs +++ b/Application/Features/Dtos/ItemTypeDto.cs @@ -1,4 +1,4 @@ -namespace Api.Responses; +namespace Application.Features.Dtos; public class ItemTypeDto { diff --git a/Application/Features/Items/CreateItem/CreateItemHandler.cs b/Application/Features/Items/CreateItem/CreateItemHandler.cs new file mode 100644 index 0000000..972e724 --- /dev/null +++ b/Application/Features/Items/CreateItem/CreateItemHandler.cs @@ -0,0 +1,54 @@ +using Core.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Application.Features.Items.CreateItem; + +public class CreateItemHandler +{ + private readonly TenantAppDbContext _context; + private readonly IClaimsProvider _claimsProvider; + + public CreateItemHandler( + TenantAppDbContext context, + IClaimsProvider claimsProvider + ) + { + _context = context; + _claimsProvider = claimsProvider; + } + + public async Task HandleAsync(CreateItemRequest createItemRequest) + { + var itemTypeIdDoesNotExistWithinTenant = await _context + .QueryableWithinTenant() + .AllAsync(x => x.Id != createItemRequest.ItemTypeId); + + if (itemTypeIdDoesNotExistWithinTenant) + { + throw new Exception($"Passed item type where id={createItemRequest.ItemTypeId} is not found within tenant where id={_claimsProvider.TenantId}"); + } + + var item = new Item + { + TenantId = _claimsProvider.TenantId, + Name = createItemRequest.Name, + SerialNumber = createItemRequest.SerialNumber, + ItemTypeId = createItemRequest.ItemTypeId, + Price = createItemRequest.Price, + Description = createItemRequest.Description, + PurchaseDate = createItemRequest.PurchaseDate, + HolderEmployeeId = createItemRequest.HolderEmployeeId + }; + + await _context + .Items + .AddAsync(item); + + await _context.SaveChangesAsync(); + + return new CreateItemResponse() + { + NewItemId = item.Id + }; + } +} diff --git a/Api/Features/Items/CreateItem/CreateItemRequest.cs b/Application/Features/Items/CreateItem/CreateItemRequest.cs similarity index 91% rename from Api/Features/Items/CreateItem/CreateItemRequest.cs rename to Application/Features/Items/CreateItem/CreateItemRequest.cs index bebc464..278a963 100644 --- a/Api/Features/Items/CreateItem/CreateItemRequest.cs +++ b/Application/Features/Items/CreateItem/CreateItemRequest.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Api.Features.Items.CreateItem; +namespace Application.Features.Items.CreateItem; public class CreateItemRequest { diff --git a/Api/Features/Items/CreateItem/CreateItemResponse.cs b/Application/Features/Items/CreateItem/CreateItemResponse.cs similarity index 63% rename from Api/Features/Items/CreateItem/CreateItemResponse.cs rename to Application/Features/Items/CreateItem/CreateItemResponse.cs index 40467ea..0ef53b9 100644 --- a/Api/Features/Items/CreateItem/CreateItemResponse.cs +++ b/Application/Features/Items/CreateItem/CreateItemResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Features.Items.CreateItem; +namespace Application.Features.Items.CreateItem; public class CreateItemResponse { diff --git a/Api/Features/Items/GetAllItems/GetAllItemsHandler.cs b/Application/Features/Items/GetAllItems/GetAllItemsHandler.cs similarity index 51% rename from Api/Features/Items/GetAllItems/GetAllItemsHandler.cs rename to Application/Features/Items/GetAllItems/GetAllItemsHandler.cs index 4d20ba4..54e4824 100644 --- a/Api/Features/Items/GetAllItems/GetAllItemsHandler.cs +++ b/Application/Features/Items/GetAllItems/GetAllItemsHandler.cs @@ -1,29 +1,26 @@ -using Api.ExternalDeps.EmployeesApi; -using Api.Responses; +using Application.ExternalDeps.EmployeesApi; +using Application.Features.Dtos; using Application.Queries; -namespace Api.Features.Items.GetAllItems; +namespace Application.Features.Items.GetAllItems; public class GetAllItemsHandler { private readonly AllItemsQuery _allItemsQuery; - private readonly EmployeesApi _employeesApi; + + public const string NotFoundEmployeeFullName = "Not Found"; public GetAllItemsHandler( - AllItemsQuery allItemsQuery, - EmployeesApi employeesApi + AllItemsQuery allItemsQuery ) { _allItemsQuery = allItemsQuery; - _employeesApi = employeesApi; } - public async Task HandleAsync() + public async Task HandleAsync(EmployeesResponse allEmployeesResponse) { var items = await _allItemsQuery.GetAsync(); - var allEmployeesResponse = await _employeesApi.GetAllEmployeesAsync(); - return new GetAllItemsResponse { Items = items @@ -40,7 +37,16 @@ public async Task HandleAsync() Price = x.Price, Description = x.Description, PurchaseDate = x.PurchaseDate, - HolderEmployee = HolderEmployeeMapper.MapToEmployeeDto(x.HolderEmployeeId, allEmployeesResponse) + HolderEmployee = x.HolderEmployeeId == null + ? null + : new EmployeeDto + { + Id = x.HolderEmployeeId.Value, + FullName = allEmployeesResponse + .Employees + .SingleOrDefault(y => y.Id == x.HolderEmployeeId.Value) + ?.FullName ?? NotFoundEmployeeFullName + } }) .ToList() }; diff --git a/Api/Features/Items/GetAllItems/HolderEmployeeMapperTests.cs b/Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs similarity index 100% rename from Api/Features/Items/GetAllItems/HolderEmployeeMapperTests.cs rename to Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs diff --git a/Api/Features/Items/GetAllItems/GetAllItemsResponse.cs b/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs similarity index 90% rename from Api/Features/Items/GetAllItems/GetAllItemsResponse.cs rename to Application/Features/Items/GetAllItems/GetAllItemsResponse.cs index 207971d..b9d50c2 100644 --- a/Api/Features/Items/GetAllItems/GetAllItemsResponse.cs +++ b/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs @@ -1,6 +1,4 @@ -using Api.Responses; - -namespace Api.Features.Items.GetAllItems; +namespace Application.Features.Items.GetAllItems; public class GetAllItemsResponse { From 12ae07a32d72e4d45ad99bde05e59baf1ac6edb4 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Thu, 27 Nov 2025 17:16:43 +0500 Subject: [PATCH 2/5] chore: add using --- Application/Features/Items/GetAllItems/GetAllItemsResponse.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs b/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs index b9d50c2..4f643ea 100644 --- a/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs +++ b/Application/Features/Items/GetAllItems/GetAllItemsResponse.cs @@ -1,4 +1,6 @@ -namespace Application.Features.Items.GetAllItems; +using Application.Features.Dtos; + +namespace Application.Features.Items.GetAllItems; public class GetAllItemsResponse { From dc426d768d99412350f75dd763899468f862dd99 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Thu, 27 Nov 2025 17:23:29 +0500 Subject: [PATCH 3/5] test: remove test --- .../GetAllItems/GetAllItemsHandlerTests.cs | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs diff --git a/Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs b/Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs deleted file mode 100644 index af563f9..0000000 --- a/Application/Features/Items/GetAllItems/GetAllItemsHandlerTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Api.ExternalDeps.EmployeesApi.Responses; -using Xunit; - -namespace Api.Features.Items.GetAllItems; - -public class HolderEmployeeMapperTests -{ - [Fact] - public void MapNonExistentEmployee_ShouldUseNotFoundAsFullName() - { - var empltyEmployeesResponse = new EmployeesResponse - { - Employees = new List() - }; - - var employeeId = 5; - - var mappedHolderEmployeeDto = HolderEmployeeMapper.MapToEmployeeDto(employeeId, empltyEmployeesResponse); - - Assert.Equal(employeeId, mappedHolderEmployeeDto!.Id); - Assert.Equal(HolderEmployeeMapper.NotFoundEmployeeFullName, mappedHolderEmployeeDto.FullName); - } -} From 656782d3eee7d007ad0f27d6eb610448871a6ee0 Mon Sep 17 00:00:00 2001 From: Anastasia Saraeva Date: Thu, 27 Nov 2025 17:26:03 +0500 Subject: [PATCH 4/5] chore: add using --- Api/Controllers/ItemTypesController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Api/Controllers/ItemTypesController.cs b/Api/Controllers/ItemTypesController.cs index 5c86dba..acb2f3e 100644 --- a/Api/Controllers/ItemTypesController.cs +++ b/Api/Controllers/ItemTypesController.cs @@ -2,6 +2,7 @@ using Api.Requests; using Api.Responses; using Application.Commands; +using Application.Features.Dtos; using Application.Queries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; From 126f94edc98863ac2beab3c15805b0cac442d400 Mon Sep 17 00:00:00 2001 From: Workflow Action Date: Thu, 27 Nov 2025 12:28:24 +0000 Subject: [PATCH 5/5] docs(readme): bring test coverage score up to date --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index af5bae1..28afcd9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # inner-circle-items-api -[![coverage](https://img.shields.io/badge/e2e_coverage-82.22%25-olivedrab)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) -[![coverage](https://img.shields.io/badge/units_coverage-17.34%25-crimson)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) -[![coverage](https://img.shields.io/badge/full_coverage-89.96%25-olivedrab)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/e2e_coverage-82.47%25-olivedrab)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/units_coverage-15.80%25-crimson)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) +[![coverage](https://img.shields.io/badge/full_coverage-89.59%25-olivedrab)](https://github.com/TourmalineCore/inner-circle-items-api/actions/workflows/calculate-tests-coverage-on-pull-request.yml) This repo contains Inner Circle Items API.