Add category products query and update endpoints

This commit is contained in:
masoodafar-web
2025-11-27 04:58:09 +03:30
parent bb8d35a1d1
commit 8878c07031
9 changed files with 242 additions and 2 deletions

View File

@@ -0,0 +1,8 @@
namespace BackOffice.BFF.Application.ProductsCQ.Commands.UpdateCategoryProducts;
public record UpdateCategoryProductsCommand : IRequest<Unit>
{
public long CategoryId { get; init; }
public IReadOnlyCollection<long> ProductIds { get; init; } = Array.Empty<long>();
}

View File

@@ -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<UpdateCategoryProductsCommand, Unit>
{
private readonly IApplicationContractContext _context;
public UpdateCategoryProductsCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(UpdateCategoryProductsCommand request, CancellationToken cancellationToken)
{
var targetIds = request.ProductIds?.ToHashSet() ?? new HashSet<long>();
// 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;
}
}

View File

@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory;
public record GetProductsForCategoryQuery : IRequest<GetProductsForCategoryResponseDto>
{
public long CategoryId { get; init; }
}

View File

@@ -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<GetProductsForCategoryQuery, GetProductsForCategoryResponseDto>
{
private readonly IApplicationContractContext _context;
public GetProductsForCategoryQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetProductsForCategoryResponseDto> 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;
}
}

View File

@@ -0,0 +1,14 @@
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductsForCategory;
public class GetProductsForCategoryResponseDto
{
public List<CategoryProductItemDto> Items { get; set; } = new();
}
public class CategoryProductItemDto
{
public long Id { get; set; }
public string Title { get; set; } = string.Empty;
public bool Selected { get; set; }
}

View File

@@ -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<GetProductsForCategoryResponseDto, GetProductsForCategoryResponse>()
.MapWith(src => new GetProductsForCategoryResponse
{
Items =
{
(src.Items ?? Enumerable.Empty<CategoryProductItemDto>())
.Select(x => new CategoryProductItem
{
Id = x.Id,
Title = x.Title ?? string.Empty,
Selected = x.Selected
})
}
});
// Map GetProductGallery response DTO to protobuf response
config.NewConfig<GetProductGalleryResponseDto, GetProductGalleryResponse>()
.MapWith(src => new GetProductGalleryResponse
{
Items =
{
(src.Items ?? Enumerable.Empty<ProductGalleryItemDto>())
.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
})
}
});
}
}

View File

@@ -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<UpdateProductCategoriesRequest, UpdateProductCategoriesCommand>(request, context);
}
public override async Task<GetProductsForCategoryResponse> GetProductsForCategory(GetProductsForCategoryRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetProductsForCategoryRequest, GetProductsForCategoryQuery, GetProductsForCategoryResponse>(request, context);
}
public override async Task<Empty> UpdateCategoryProducts(UpdateCategoryProductsRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateCategoryProductsRequest, UpdateCategoryProductsCommand>(request, context);
}
}

View File

@@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>0.0.3</Version>
<Version>0.0.4</Version>
<DebugType>None</DebugType>
<DebugSymbols>False</DebugSymbols>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>

View File

@@ -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;
}