b42d9e141d
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 3m9s
- Add RequiresPermissionAttribute for gRPC method access control. - Create IFileManagementService interface for file upload and management. - Implement AddProductImageCommand and handler for adding product images. - Implement CreateNewProductsCommand and handler for creating new products with image uploads. - Implement DeleteProductsCommand and handler for deleting products and their associations. - Implement RemoveProductImageCommand and handler for removing product images from galleries. - Implement UpdateProductsCommand and handler for updating product details and images. - Create GetProductGalleryQuery and handler for retrieving product galleries. - Implement PermissionService for role-based access control using JWT claims. - Implement FileManagementService for handling file uploads and image optimization. - Define gRPC service and messages for file management in fms.proto. - Add FluentValidation for request validation in various commands. - Create PermissionInterceptor for enforcing permissions on gRPC methods.
140 lines
5.1 KiB
C#
140 lines
5.1 KiB
C#
using CMSMicroservice.Application.Common.Interfaces;
|
|
using CMSMicroservice.Protobuf.Protos.FMS;
|
|
using Google.Protobuf;
|
|
using Grpc.Net.Client;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
|
using SixLabors.ImageSharp.Processing;
|
|
using System.IO;
|
|
|
|
namespace CMSMicroservice.Infrastructure.Services;
|
|
|
|
public class FileManagementService : IFileManagementService, IDisposable
|
|
{
|
|
private readonly ILogger<FileManagementService> _logger;
|
|
private readonly FileInfoContract.FileInfoContractClient _client;
|
|
private readonly GrpcChannel _channel;
|
|
|
|
private const int MainImageMaxWidth = 1200;
|
|
private const int MainImageMaxHeight = 1200;
|
|
private const int ThumbnailMaxWidth = 300;
|
|
private const int ThumbnailMaxHeight = 300;
|
|
private const int JpegQuality = 75;
|
|
|
|
public FileManagementService(IConfiguration configuration, ILogger<FileManagementService> logger)
|
|
{
|
|
_logger = logger;
|
|
|
|
var fmsAddress = configuration["FMS:Address"] ?? "https://dl.afrino.co";
|
|
|
|
_channel = GrpcChannel.ForAddress(fmsAddress, new GrpcChannelOptions
|
|
{
|
|
MaxReceiveMessageSize = 100 * 1024 * 1024, // 100 MB
|
|
MaxSendMessageSize = 100 * 1024 * 1024
|
|
});
|
|
|
|
_client = new FileInfoContract.FileInfoContractClient(_channel);
|
|
}
|
|
|
|
public async Task<string?> UploadFileAsync(string directory, byte[] fileBytes, string mime, string? fileName,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var request = new CreateNewFileInfoRequest
|
|
{
|
|
Directory = directory,
|
|
File = ByteString.CopyFrom(fileBytes),
|
|
Mime = mime,
|
|
IsBase64 = false
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(fileName))
|
|
request.FileName = fileName;
|
|
|
|
var response = await _client.CreateNewFileInfoAsync(request, cancellationToken: cancellationToken);
|
|
|
|
if (response != null && !string.IsNullOrWhiteSpace(response.File))
|
|
{
|
|
_logger.LogInformation("File uploaded to FMS successfully. Id: {Id}, Path: {Path}", response.Id, response.File);
|
|
return response.File;
|
|
}
|
|
|
|
_logger.LogWarning("FMS upload returned null or empty path for file: {FileName}", fileName);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error uploading file to FMS. Directory: {Directory}, FileName: {FileName}", directory, fileName);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<(string? MainImagePath, string? ThumbnailPath)> UploadImageWithThumbnailAsync(
|
|
string directory, byte[] fileBytes, string mime, string? fileName,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
string? mainImagePath = null;
|
|
string? thumbnailPath = null;
|
|
|
|
try
|
|
{
|
|
// Optimize main image
|
|
var mainImageBytes = await OptimizeImageAsync(fileBytes, MainImageMaxWidth, MainImageMaxHeight);
|
|
mainImagePath = await UploadFileAsync(directory, mainImageBytes, "image/jpeg", fileName, cancellationToken);
|
|
|
|
// Create and upload thumbnail
|
|
var thumbnailBytes = await OptimizeImageAsync(fileBytes, ThumbnailMaxWidth, ThumbnailMaxHeight);
|
|
var thumbFileName = fileName != null ? $"thumb_{fileName}" : null;
|
|
thumbnailPath = await UploadFileAsync($"{directory}/Thumbnails", thumbnailBytes, "image/jpeg", thumbFileName, cancellationToken);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error processing and uploading image with thumbnail. Directory: {Directory}", directory);
|
|
}
|
|
|
|
return (mainImagePath, thumbnailPath);
|
|
}
|
|
|
|
public async Task<bool> DeleteFileAsync(long fileId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var request = new DeleteFileInfoRequest { Id = fileId };
|
|
var response = await _client.DeleteFileInfoAsync(request, cancellationToken: cancellationToken);
|
|
return response?.Success ?? false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error deleting file from FMS. FileId: {FileId}", fileId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static async Task<byte[]> OptimizeImageAsync(byte[] imageBytes, int maxWidth, int maxHeight)
|
|
{
|
|
using var image = Image.Load(imageBytes);
|
|
|
|
// Only resize if larger than max dimensions
|
|
if (image.Width > maxWidth || image.Height > maxHeight)
|
|
{
|
|
image.Mutate(x => x.Resize(new ResizeOptions
|
|
{
|
|
Size = new Size(maxWidth, maxHeight),
|
|
Mode = ResizeMode.Max
|
|
}));
|
|
}
|
|
|
|
using var ms = new MemoryStream();
|
|
await image.SaveAsJpegAsync(ms, new JpegEncoder { Quality = JpegQuality });
|
|
return ms.ToArray();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_channel?.Dispose();
|
|
}
|
|
}
|