This commit is contained in:
masoodafar-web
2025-11-27 03:14:43 +03:30
parent b18e131e31
commit ad3cc728df
13 changed files with 318 additions and 2 deletions

View File

@@ -5,6 +5,8 @@ using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserOrder;
using CMSMicroservice.Protobuf.Protos.UserRole;
using CMSMicroservice.Protobuf.Protos.Products;
using CMSMicroservice.Protobuf.Protos.ProductImages;
using CMSMicroservice.Protobuf.Protos.ProductGallerys;
using FMSMicroservice.Protobuf.Protos.FileInfo;
namespace BackOffice.BFF.Application.Common.Interfaces;
@@ -17,6 +19,8 @@ public interface IApplicationContractContext
#region CMS
PackageContract.PackageContractClient Packages { get; }
ProductsContract.ProductsContractClient Products { get; }
ProductImagesContract.ProductImagesContractClient ProductImages { get; }
ProductGallerysContract.ProductGallerysContractClient ProductGallerys { get; }
RoleContract.RoleContractClient Roles { get; }
UserAddressContract.UserAddressContractClient UserAddress { get; }
UserContract.UserContractClient Users { get; }

View File

@@ -0,0 +1,16 @@
namespace BackOffice.BFF.Application.ProductsCQ.Commands.AddProductImage;
public record AddProductImageCommand : IRequest<AddProductImageResponseDto>
{
public long ProductId { get; init; }
public string Title { get; init; }
public ImageFileModel ImageFile { get; init; }
}
public class ImageFileModel
{
public byte[] File { get; set; }
public string FileName { get; set; }
public string Mime { get; set; }
}

View File

@@ -0,0 +1,95 @@
using System.IO;
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ProductImages;
using CMSMicroservice.Protobuf.Protos.ProductGallerys;
using Google.Protobuf;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
namespace BackOffice.BFF.Application.ProductsCQ.Commands.AddProductImage;
public class AddProductImageCommandHandler : IRequestHandler<AddProductImageCommand, AddProductImageResponseDto>
{
private readonly IApplicationContractContext _context;
public AddProductImageCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<AddProductImageResponseDto> Handle(AddProductImageCommand request, CancellationToken cancellationToken)
{
if (request.ImageFile == null || request.ImageFile.File == null || request.ImageFile.File.Length == 0)
{
throw new ArgumentException("Image file is required.");
}
var optimizedMainImage = OptimizeImage(request.ImageFile.File, 1200, 1200, 80);
var thumbnailImage = OptimizeImage(request.ImageFile.File, 300, 300, 75);
var mainFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/Products/Gallery",
IsBase64 = false,
MIME = request.ImageFile.Mime,
FileName = request.ImageFile.FileName,
File = ByteString.CopyFrom(optimizedMainImage)
}, cancellationToken: cancellationToken);
var thumbFileInfo = await _context.FileInfos.CreateNewFileInfoAsync(new()
{
Directory = "Images/Products/Gallery/Thumbnail",
IsBase64 = false,
MIME = request.ImageFile.Mime,
FileName = request.ImageFile.FileName,
File = ByteString.CopyFrom(thumbnailImage)
}, cancellationToken: cancellationToken);
if (mainFileInfo == null || string.IsNullOrWhiteSpace(mainFileInfo.File) ||
thumbFileInfo == null || string.IsNullOrWhiteSpace(thumbFileInfo.File))
{
throw new InvalidOperationException("Error while uploading gallery image.");
}
var createImageResponse = await _context.ProductImages.CreateNewProductImagesAsync(new CreateNewProductImagesRequest
{
Title = request.Title ?? string.Empty,
ImagePath = mainFileInfo.File,
ImageThumbnailPath = thumbFileInfo.File
}, cancellationToken: cancellationToken);
var createGalleryResponse = await _context.ProductGallerys.CreateNewProductGallerysAsync(new CreateNewProductGallerysRequest
{
ProductId = request.ProductId,
ProductImageId = createImageResponse.Id
}, cancellationToken: cancellationToken);
return new AddProductImageResponseDto
{
ProductGalleryId = createGalleryResponse.Id,
ProductImageId = createImageResponse.Id,
Title = request.Title ?? string.Empty,
ImagePath = mainFileInfo.File,
ImageThumbnailPath = thumbFileInfo.File
};
}
private static byte[] OptimizeImage(byte[] original, int maxWidth, int maxHeight, int quality)
{
using var image = Image.Load(original);
var ratio = Math.Min(maxWidth / (float)image.Width, maxHeight / (float)image.Height);
if (ratio < 1f)
{
var width = (int)(image.Width * ratio);
var height = (int)(image.Height * ratio);
image.Mutate(x => x.Resize(width, height));
}
using var ms = new MemoryStream();
image.Save(ms, new JpegEncoder { Quality = quality });
return ms.ToArray();
}
}

View File

@@ -0,0 +1,11 @@
namespace BackOffice.BFF.Application.ProductsCQ.Commands.AddProductImage;
public class AddProductImageResponseDto
{
public long ProductGalleryId { get; set; }
public long ProductImageId { get; set; }
public string Title { get; set; }
public string ImagePath { get; set; }
public string ImageThumbnailPath { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.ProductsCQ.Commands.RemoveProductImage;
public record RemoveProductImageCommand : IRequest<Unit>
{
public long ProductGalleryId { get; init; }
}

View File

@@ -0,0 +1,25 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ProductGallerys;
namespace BackOffice.BFF.Application.ProductsCQ.Commands.RemoveProductImage;
public class RemoveProductImageCommandHandler : IRequestHandler<RemoveProductImageCommand, Unit>
{
private readonly IApplicationContractContext _context;
public RemoveProductImageCommandHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<Unit> Handle(RemoveProductImageCommand request, CancellationToken cancellationToken)
{
await _context.ProductGallerys.DeleteProductGallerysAsync(new DeleteProductGallerysRequest
{
Id = request.ProductGalleryId
}, cancellationToken: cancellationToken);
return Unit.Value;
}
}

View File

@@ -0,0 +1,7 @@
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery;
public record GetProductGalleryQuery : IRequest<GetProductGalleryResponseDto>
{
public long ProductId { get; init; }
}

View File

@@ -0,0 +1,55 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ProductGallerys;
using CMSMicroservice.Protobuf.Protos.ProductImages;
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery;
public class GetProductGalleryQueryHandler : IRequestHandler<GetProductGalleryQuery, GetProductGalleryResponseDto>
{
private readonly IApplicationContractContext _context;
public GetProductGalleryQueryHandler(IApplicationContractContext context)
{
_context = context;
}
public async Task<GetProductGalleryResponseDto> Handle(GetProductGalleryQuery request, CancellationToken cancellationToken)
{
var galleryRequest = new GetAllProductGallerysByFilterRequest
{
Filter = new GetAllProductGallerysByFilterFilter()
};
var galleryResponse = await _context.ProductGallerys.GetAllProductGallerysByFilterAsync(galleryRequest, cancellationToken: cancellationToken);
// Filter by product id on client side because generated type may not support assigning Int64Value directly
var filteredModels = galleryResponse?.Models?.Where(x => x.ProductId == request.ProductId).ToList();
var result = new GetProductGalleryResponseDto();
if (filteredModels == null || filteredModels.Count == 0)
return result;
foreach (var item in filteredModels)
{
var image = await _context.ProductImages.GetProductImagesAsync(new GetProductImagesRequest
{
Id = item.ProductImageId
}, cancellationToken: cancellationToken);
if (image == null)
continue;
result.Items.Add(new ProductGalleryItemDto
{
ProductGalleryId = item.Id,
ProductImageId = item.ProductImageId,
Title = image.Title,
ImagePath = image.ImagePath,
ImageThumbnailPath = image.ImageThumbnailPath
});
}
return result;
}
}

View File

@@ -0,0 +1,16 @@
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery;
public class GetProductGalleryResponseDto
{
public List<ProductGalleryItemDto> Items { get; set; } = new();
}
public class ProductGalleryItemDto
{
public long ProductGalleryId { get; set; }
public long ProductImageId { get; set; }
public string Title { get; set; }
public string ImagePath { get; set; }
public string ImageThumbnailPath { get; set; }
}

View File

@@ -6,6 +6,8 @@ using CMSMicroservice.Protobuf.Protos.UserAddress;
using CMSMicroservice.Protobuf.Protos.UserOrder;
using CMSMicroservice.Protobuf.Protos.UserRole;
using CMSMicroservice.Protobuf.Protos.Products;
using CMSMicroservice.Protobuf.Protos.ProductImages;
using CMSMicroservice.Protobuf.Protos.ProductGallerys;
using FMSMicroservice.Protobuf.Protos.FileInfo;
using Microsoft.Extensions.DependencyInjection;
@@ -41,6 +43,8 @@ public class ApplicationContractContext : IApplicationContractContext
#region CMS
public PackageContract.PackageContractClient Packages => GetService<PackageContract.PackageContractClient>();
public ProductsContract.ProductsContractClient Products => GetService<ProductsContract.ProductsContractClient>();
public ProductImagesContract.ProductImagesContractClient ProductImages => GetService<ProductImagesContract.ProductImagesContractClient>();
public ProductGallerysContract.ProductGallerysContractClient ProductGallerys => GetService<ProductGallerysContract.ProductGallerysContractClient>();
public RoleContract.RoleContractClient Roles => GetService<RoleContract.RoleContractClient>();
public UserAddressContract.UserAddressContractClient UserAddress => GetService<UserAddressContract.UserAddressContractClient>();
public UserContract.UserContractClient Users => GetService<UserContract.UserContractClient>();

View File

@@ -5,6 +5,9 @@ using BackOffice.BFF.Application.ProductsCQ.Commands.UpdateProducts;
using BackOffice.BFF.Application.ProductsCQ.Commands.DeleteProducts;
using BackOffice.BFF.Application.ProductsCQ.Queries.GetProducts;
using BackOffice.BFF.Application.ProductsCQ.Queries.GetAllProductsByFilter;
using BackOffice.BFF.Application.ProductsCQ.Commands.AddProductImage;
using BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery;
using BackOffice.BFF.Application.ProductsCQ.Commands.RemoveProductImage;
namespace BackOffice.BFF.WebApi.Services;
@@ -41,5 +44,19 @@ public class ProductsService : ProductsContract.ProductsContractBase
{
return await _dispatchRequestToCQRS.Handle<GetAllProductsByFilterRequest, GetAllProductsByFilterQuery, GetAllProductsByFilterResponse>(request, context);
}
}
public override async Task<AddProductImageResponse> AddProductImage(AddProductImageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<AddProductImageRequest, AddProductImageCommand, AddProductImageResponse>(request, context);
}
public override async Task<GetProductGalleryResponse> GetProductGallery(GetProductGalleryRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetProductGalleryRequest, GetProductGalleryQuery, GetProductGalleryResponse>(request, context);
}
public override async Task<Empty> RemoveProductImage(RemoveProductImageRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RemoveProductImageRequest, RemoveProductImageCommand>(request, context);
}
}

View File

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

View File

@@ -40,6 +40,26 @@ service ProductsContract
get: "/GetAllProductsByFilter"
};
};
rpc AddProductImage(AddProductImageRequest) returns (AddProductImageResponse){
option (google.api.http) = {
post: "/AddProductImage"
body: "*"
};
};
rpc GetProductGallery(GetProductGalleryRequest) returns (GetProductGalleryResponse){
option (google.api.http) = {
get: "/GetProductGallery"
};
};
rpc RemoveProductImage(RemoveProductImageRequest) returns (google.protobuf.Empty){
option (google.api.http) = {
delete: "/RemoveProductImage"
body: "*"
};
};
}
message ImageFileModel
@@ -174,3 +194,42 @@ message DecimalValue
sfixed32 nanos = 2;
}
message AddProductImageRequest
{
int64 product_id = 1;
string title = 2;
ImageFileModel image_file = 3;
}
message AddProductImageResponse
{
int64 product_gallery_id = 1;
int64 product_image_id = 2;
string title = 3;
string image_path = 4;
string image_thumbnail_path = 5;
}
message GetProductGalleryRequest
{
int64 product_id = 1;
}
message ProductGalleryItem
{
int64 product_gallery_id = 1;
int64 product_image_id = 2;
string title = 3;
string image_path = 4;
string image_thumbnail_path = 5;
}
message GetProductGalleryResponse
{
repeated ProductGalleryItem items = 1;
}
message RemoveProductImageRequest
{
int64 product_gallery_id = 1;
}