update 2
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BackOffice.BFF.Application.ProductsCQ.Commands.RemoveProductImage;
|
||||
|
||||
public record RemoveProductImageCommand : IRequest<Unit>
|
||||
{
|
||||
public long ProductGalleryId { get; init; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BackOffice.BFF.Application.ProductsCQ.Queries.GetProductGallery;
|
||||
|
||||
public record GetProductGalleryQuery : IRequest<GetProductGalleryResponseDto>
|
||||
{
|
||||
public long ProductId { get; init; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user