thecr3w-dev/backend/TheCr3w.Shared/Storage/R2StorageService.cs

97 lines
3.4 KiB
C#

using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
namespace TheCr3w.Shared.Storage;
public class R2StorageOptions
{
public string Endpoint { get; set; } = "https://bf705752e38169fedb83efd5b747390e.r2.cloudflarestorage.com";
public string BucketName { get; set; } = "thecr3w-app";
public string Region { get; set; } = "auto";
public string AccessKeyId { get; set; } = string.Empty;
public string SecretAccessKey { get; set; } = string.Empty;
}
public interface IR2StorageService
{
string GeneratePreSignedDownloadUrl(string objectKey, TimeSpan? duration = null);
string GeneratePreSignedUploadUrl(string objectKey, string contentType, TimeSpan? duration = null);
Task<bool> ObjectExistsAsync(string objectKey, CancellationToken cancellationToken = default);
}
public class R2StorageService : IR2StorageService
{
private readonly R2StorageOptions _options;
private readonly IAmazonS3 _s3Client;
public R2StorageService(R2StorageOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
var credentials = new BasicAWSCredentials(_options.AccessKeyId, _options.SecretAccessKey);
var config = new AmazonS3Config
{
ServiceURL = _options.Endpoint,
AuthenticationRegion = _options.Region,
ForcePathStyle = true
};
_s3Client = new AmazonS3Client(credentials, config);
}
/// <summary>
/// Generuje krótko żyjący (domyślnie 15 minut) podpisany adres URL (Pre-signed S3 GET)
/// pozwalający na bezpośrednie pobranie artefaktu z Cloudflare R2 bez obciążania serwera.
/// </summary>
public string GeneratePreSignedDownloadUrl(string objectKey, TimeSpan? duration = null)
{
var expires = DateTime.UtcNow.Add(duration ?? TimeSpan.FromMinutes(15));
var request = new GetPreSignedUrlRequest
{
BucketName = _options.BucketName,
Key = objectKey,
Verb = HttpVerb.GET,
Expires = expires
};
return _s3Client.GetPreSignedURL(request);
}
/// <summary>
/// Generuje krótko żyjący (domyślnie 30 minut) podpisany adres URL (Pre-signed S3 PUT)
/// pozwalający na bezpieczny upload pliku do Cloudflare R2.
/// </summary>
public string GeneratePreSignedUploadUrl(string objectKey, string contentType, TimeSpan? duration = null)
{
var expires = DateTime.UtcNow.Add(duration ?? TimeSpan.FromMinutes(30));
var request = new GetPreSignedUrlRequest
{
BucketName = _options.BucketName,
Key = objectKey,
Verb = HttpVerb.PUT,
Expires = expires,
ContentType = contentType
};
return _s3Client.GetPreSignedURL(request);
}
public async Task<bool> ObjectExistsAsync(string objectKey, CancellationToken cancellationToken = default)
{
try
{
var response = await _s3Client.GetObjectMetadataAsync(new GetObjectMetadataRequest
{
BucketName = _options.BucketName,
Key = objectKey
}, cancellationToken);
return response.HttpStatusCode == System.Net.HttpStatusCode.OK;
}
catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return false;
}
}
}