using System.Security.Cryptography; using Microsoft.Extensions.Options; using Minio; using Minio.DataModel.Args; public class DocumentStorageService : IDocumentStorageService { private readonly IMinioClient _minio; private readonly MinioOptions _options; public DocumentStorageService(IMinioClient minio, IOptions options) { _minio = minio; _options = options.Value; } public async Task<(string objectKey, string sha256, long fileSize)> UploadAsync( Stream fileStream, string contentType, Guid batchId) { var now = DateTimeOffset.UtcNow; using var sha256 = SHA256.Create(); using var memStream = new MemoryStream(); await fileStream.CopyToAsync(memStream); memStream.Position = 0; var hashBytes = sha256.ComputeHash(memStream); var hashHex = Convert.ToHexString(hashBytes).ToLowerInvariant(); memStream.Position = 0; var extension = contentType switch { "application/pdf" => "pdf", "image/jpeg" => "jpg", "image/png" => "png", _ => "bin" }; var objectKey = $"scans/{now.Year}/{now.Month:D2}/{batchId}/{hashHex}.{extension}"; await EnsureBucketAsync(); await _minio.PutObjectAsync(new PutObjectArgs() .WithBucket(_options.BucketName) .WithObject(objectKey) .WithStreamData(memStream) .WithObjectSize(memStream.Length) .WithContentType(contentType)); return (objectKey, hashHex, memStream.Length); } public async Task GetPresignedUrlAsync(string objectKey) { return await _minio.PresignedGetObjectAsync(new PresignedGetObjectArgs() .WithBucket(_options.BucketName) .WithObject(objectKey) .WithExpiry(_options.PresignedUrlExpiryMinutes * 60)); } public async Task DownloadAsync(string objectKey) { var memStream = new MemoryStream(); await _minio.GetObjectAsync(new GetObjectArgs() .WithBucket(_options.BucketName) .WithObject(objectKey) .WithCallbackStream(stream => stream.CopyTo(memStream))); memStream.Position = 0; return memStream; } private async Task EnsureBucketAsync() { var exists = await _minio.BucketExistsAsync(new BucketExistsArgs().WithBucket(_options.BucketName)); if (!exists) await _minio.MakeBucketAsync(new MakeBucketArgs().WithBucket(_options.BucketName)); } }