From 8878c070318c48c83baf7c106090693a44f4ceb3 Mon Sep 17 00:00:00 2001 From: masoodafar-web Date: Thu, 27 Nov 2025 04:58:09 +0330 Subject: [PATCH] Add category products query and update endpoints --- .../UpdateCategoryProductsCommand.cs | 8 +++ .../UpdateCategoryProductsCommandHandler.cs | 62 +++++++++++++++++ .../GetProductsForCategoryQuery.cs | 7 ++ .../GetProductsForCategoryQueryHandler.cs | 66 +++++++++++++++++++ .../GetProductsForCategoryResponseDto.cs | 14 ++++ .../Common/Mappings/ProductsProfile.cs | 37 ++++++++++- .../Services/ProductsService.cs | 12 ++++ .../BackOffice.BFF.Products.Protobuf.csproj | 2 +- .../Protos/products.proto | 36 ++++++++++ 9 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommand.cs create mode 100644 src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs create mode 100644 src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQuery.cs create mode 100644 src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs create mode 100644 src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryResponseDto.cs diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommand.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommand.cs new file mode 100644 index 0000000..cc7f442 --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommand.cs @@ -0,0 +1,8 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Commands.UpdateCategoryProducts; + +public record UpdateCategoryProductsCommand : IRequest +{ + public long CategoryId { get; init; } + public IReadOnlyCollection ProductIds { get; init; } = Array.Empty(); +} + diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs new file mode 100644 index 0000000..a17d32c --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Commands/UpdateCategoryProducts/UpdateCategoryProductsCommandHandler.cs @@ -0,0 +1,62 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSPruductCategory = CMSMicroservice.Protobuf.Protos.PruductCategory; + +namespace BackOffice.BFF.Application.ProductsCQ.Commands.UpdateCategoryProducts; + +public class UpdateCategoryProductsCommandHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public UpdateCategoryProductsCommandHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(UpdateCategoryProductsCommand request, CancellationToken cancellationToken) + { + var targetIds = request.ProductIds?.ToHashSet() ?? new HashSet(); + + // load existing links for this category + var existingRequest = new CMSPruductCategory.GetAllPruductCategoryByFilterRequest + { + Filter = new CMSPruductCategory.GetAllPruductCategoryByFilterFilter + { + CategoryId = request.CategoryId + }, + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState + { + PageNumber = 1, + PageSize = 1000 + } + }; + + var existingResponse = await _context.ProductCategories.GetAllPruductCategoryByFilterAsync(existingRequest, cancellationToken: cancellationToken); + var existing = existingResponse.Models ?? new(); + + var existingIds = existing.Select(x => x.ProductId).ToHashSet(); + + var toAdd = targetIds.Except(existingIds).ToList(); + var toRemove = existing.Where(x => !targetIds.Contains(x.ProductId)).ToList(); + + foreach (var productId in toAdd) + { + var createRequest = new CMSPruductCategory.CreateNewPruductCategoryRequest + { + ProductId = productId, + CategoryId = request.CategoryId + }; + + await _context.ProductCategories.CreateNewPruductCategoryAsync(createRequest, cancellationToken: cancellationToken); + } + + foreach (var rel in toRemove) + { + await _context.ProductCategories.DeletePruductCategoryAsync( + new CMSPruductCategory.DeletePruductCategoryRequest { Id = rel.Id }, + cancellationToken: cancellationToken); + } + + return Unit.Value; + } +} + diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQuery.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQuery.cs new file mode 100644 index 0000000..2c7b6ed --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQuery.cs @@ -0,0 +1,7 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; + +public record GetProductsForCategoryQuery : IRequest +{ + public long CategoryId { get; init; } +} + diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs new file mode 100644 index 0000000..54962cc --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryQueryHandler.cs @@ -0,0 +1,66 @@ +using BackOffice.BFF.Application.Common.Interfaces; +using CMSProducts = CMSMicroservice.Protobuf.Protos.Products; +using CMSPruductCategory = CMSMicroservice.Protobuf.Protos.PruductCategory; + +namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; + +public class GetProductsForCategoryQueryHandler : IRequestHandler +{ + private readonly IApplicationContractContext _context; + + public GetProductsForCategoryQueryHandler(IApplicationContractContext context) + { + _context = context; + } + + public async Task Handle(GetProductsForCategoryQuery request, CancellationToken cancellationToken) + { + // Load all products + var productsRequest = new CMSProducts.GetAllProductsByFilterRequest + { + Filter = new CMSProducts.GetAllProductsByFilterFilter(), + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState + { + PageNumber = 1, + PageSize = 1000 + } + }; + + var productsResponse = await _context.Products.GetAllProductsByFilterAsync(productsRequest, cancellationToken: cancellationToken); + var products = productsResponse.Models ?? new(); + + // Load links for this category + var linksRequest = new CMSPruductCategory.GetAllPruductCategoryByFilterRequest + { + Filter = new CMSPruductCategory.GetAllPruductCategoryByFilterFilter + { + CategoryId = request.CategoryId + }, + PaginationState = new CMSMicroservice.Protobuf.Protos.PaginationState + { + PageNumber = 1, + PageSize = 1000 + } + }; + + var linksResponse = await _context.ProductCategories.GetAllPruductCategoryByFilterAsync(linksRequest, cancellationToken: cancellationToken); + var links = linksResponse.Models ?? new(); + + var selectedProductIds = links.Select(l => l.ProductId).ToHashSet(); + + var result = new GetProductsForCategoryResponseDto + { + Items = products + .Select(p => new CategoryProductItemDto + { + Id = p.Id, + Title = p.Title, + Selected = selectedProductIds.Contains(p.Id) + }) + .ToList() + }; + + return result; + } +} + diff --git a/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryResponseDto.cs b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryResponseDto.cs new file mode 100644 index 0000000..7976c9c --- /dev/null +++ b/src/BackOffice.BFF.Application/ProductsCQ/Queries/GetProductsForCategory/GetProductsForCategoryResponseDto.cs @@ -0,0 +1,14 @@ +namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; + +public class GetProductsForCategoryResponseDto +{ + public List Items { get; set; } = new(); +} + +public class CategoryProductItemDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public bool Selected { get; set; } +} + diff --git a/src/BackOffice.BFF.WebApi/Common/Mappings/ProductsProfile.cs b/src/BackOffice.BFF.WebApi/Common/Mappings/ProductsProfile.cs index f0312dc..e23b33f 100644 --- a/src/BackOffice.BFF.WebApi/Common/Mappings/ProductsProfile.cs +++ b/src/BackOffice.BFF.WebApi/Common/Mappings/ProductsProfile.cs @@ -1,6 +1,8 @@ namespace BackOffice.BFF.WebApi.Common.Mappings; using BackOffice.BFF.Application.ProductsCQ.Queries.GetCategories; +using BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; +using BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery; using BackOffice.BFF.Products.Protobuf.Protos.Products; public class ProductsProfile : IRegister @@ -22,6 +24,39 @@ public class ProductsProfile : IRegister }) } }); + + // Map GetProductsForCategory response DTO to protobuf response + config.NewConfig() + .MapWith(src => new GetProductsForCategoryResponse + { + Items = + { + (src.Items ?? Enumerable.Empty()) + .Select(x => new CategoryProductItem + { + Id = x.Id, + Title = x.Title ?? string.Empty, + Selected = x.Selected + }) + } + }); + + // Map GetProductGallery response DTO to protobuf response + config.NewConfig() + .MapWith(src => new GetProductGalleryResponse + { + Items = + { + (src.Items ?? Enumerable.Empty()) + .Select(x => new ProductGalleryItem + { + ProductGalleryId = x.ProductGalleryId, + ProductImageId = x.ProductImageId, + Title = x.Title ?? string.Empty, + ImagePath = x.ImagePath ?? string.Empty, + ImageThumbnailPath = x.ImageThumbnailPath ?? string.Empty + }) + } + }); } } - diff --git a/src/BackOffice.BFF.WebApi/Services/ProductsService.cs b/src/BackOffice.BFF.WebApi/Services/ProductsService.cs index 4befc01..ddbc247 100644 --- a/src/BackOffice.BFF.WebApi/Services/ProductsService.cs +++ b/src/BackOffice.BFF.WebApi/Services/ProductsService.cs @@ -10,6 +10,8 @@ using BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery; using BackOffice.BFF.Application.ProductsCQ.Commands.RemoveProductImage; using BackOffice.BFF.Application.ProductsCQ.Queries.GetCategories; using BackOffice.BFF.Application.ProductsCQ.Commands.UpdateProductCategories; +using BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory; +using BackOffice.BFF.Application.ProductsCQ.Commands.UpdateCategoryProducts; namespace BackOffice.BFF.WebApi.Services; @@ -71,4 +73,14 @@ public class ProductsService : ProductsContract.ProductsContractBase { return await _dispatchRequestToCQRS.Handle(request, context); } + + public override async Task GetProductsForCategory(GetProductsForCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateCategoryProducts(UpdateCategoryProductsRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } } diff --git a/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj b/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj index 1c097e5..dbc3c48 100644 --- a/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj +++ b/src/Protobufs/BackOffice.BFF.Products.Protobuf/BackOffice.BFF.Products.Protobuf.csproj @@ -4,7 +4,7 @@ net9.0 enable enable - 0.0.3 + 0.0.4 None False False diff --git a/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto b/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto index a5a2fdf..ce771ed 100644 --- a/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto +++ b/src/Protobufs/BackOffice.BFF.Products.Protobuf/Protos/products.proto @@ -73,6 +73,19 @@ service ProductsContract body: "*" }; }; + + rpc GetProductsForCategory(GetProductsForCategoryRequest) returns (GetProductsForCategoryResponse){ + option (google.api.http) = { + get: "/GetProductsForCategory" + }; + }; + + rpc UpdateCategoryProducts(UpdateCategoryProductsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + post: "/UpdateCategoryProducts" + body: "*" + }; + }; } message ImageFileModel @@ -269,3 +282,26 @@ message UpdateProductCategoriesRequest int64 product_id = 1; repeated int64 category_ids = 2; } + +message CategoryProductItem +{ + int64 id = 1; + string title = 2; + bool selected = 3; +} + +message GetProductsForCategoryRequest +{ + int64 category_id = 1; +} + +message GetProductsForCategoryResponse +{ + repeated CategoryProductItem items = 1; +} + +message UpdateCategoryProductsRequest +{ + int64 category_id = 1; + repeated int64 product_ids = 2; +}