110 lines
4.5 KiB
C#
110 lines
4.5 KiB
C#
using PostFundManagement.Application.Services.Shared;
|
|
using PostFundManagement.Domain.Errors;
|
|
|
|
namespace PostFundManagement.Application.Abstractions.Services;
|
|
|
|
public abstract class S3ServiceBase(IAmazonS3 amazonS3)
|
|
{
|
|
protected readonly IAmazonS3 Client = amazonS3;
|
|
|
|
protected abstract string BucketName { get; }
|
|
protected abstract string CdnBaseUrl { get; }
|
|
|
|
public string GenerateCdnUrl(string fileKey) => $"{CdnBaseUrl}/{fileKey}";
|
|
|
|
public virtual async Task<Result<string>> GeneratePresignedUrlAsync(string fileKey, TimeSpan duration, string? contentType = null, CancellationToken cancellationToken = default) =>
|
|
await Result.Try((Func<Task<Result<string>>>)(async () =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BucketName))
|
|
return Result.Fail(ValidationError.Create("Bucket name is not configured."));
|
|
|
|
if (string.IsNullOrWhiteSpace(fileKey))
|
|
return Result.Fail(ValidationError.Create("File key is required."));
|
|
|
|
var request = new GetPreSignedUrlRequest
|
|
{
|
|
BucketName = BucketName,
|
|
Key = fileKey,
|
|
Expires = DateTime.UtcNow.Add(duration),
|
|
Verb = HttpVerb.GET
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(contentType))
|
|
{
|
|
request.ResponseHeaderOverrides.ContentType = contentType;
|
|
request.ResponseHeaderOverrides.ContentDisposition = "inline";
|
|
}
|
|
|
|
var presignedUrl = await Client.GetPreSignedURLAsync(request);
|
|
|
|
return Result.FailIf(string.IsNullOrWhiteSpace(presignedUrl),
|
|
CreateError.Create("Failed to create presigned URL"))
|
|
.ToResult(presignedUrl);
|
|
}));
|
|
|
|
private static string CreateFileKey(string path, string fileName, string fileHash)
|
|
{
|
|
var cleanSubPath = path.Replace('\\', '/').Trim('/');
|
|
|
|
return string.IsNullOrWhiteSpace(cleanSubPath)
|
|
? $"{fileHash.ToLower(CultureInfo.InvariantCulture)}_{fileName}"
|
|
: $"{cleanSubPath}/{fileHash.ToLower(CultureInfo.InvariantCulture)}_{fileName}";
|
|
}
|
|
|
|
public virtual async Task<Result<string>> UploadFileAsync(string fileName, string subPath, Stream fileStream, string contentType, CancellationToken cancellationToken = default) =>
|
|
await Result.Try((Func<Task<Result<string>>>)(async () =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BucketName))
|
|
return Result.Fail<string>(ValidationError.Create("Bucket name is not configured."));
|
|
|
|
if (string.IsNullOrWhiteSpace(CdnBaseUrl))
|
|
return Result.Fail<string>(ValidationError.Create("CDN base URL is not configured."));
|
|
|
|
await using var stream = new MemoryStream();
|
|
|
|
await fileStream.CopyToAsync(stream, cancellationToken);
|
|
await fileStream.DisposeAsync();
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
var fileHash = HashService.StreamToSha256Hash(stream);
|
|
|
|
if (string.IsNullOrWhiteSpace(fileHash))
|
|
return Result.Fail<string>(CreateError.Create("Failed to compute file hash."));
|
|
|
|
var fileKey = CreateFileKey(subPath, fileName, fileHash);
|
|
|
|
var putRequest = new PutObjectRequest
|
|
{
|
|
BucketName = BucketName,
|
|
Key = fileKey,
|
|
InputStream = stream,
|
|
ContentType = contentType,
|
|
UseChunkEncoding = false,
|
|
};
|
|
|
|
stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
var response = await Client.PutObjectAsync(putRequest, cancellationToken);
|
|
|
|
return response.HttpStatusCode != HttpStatusCode.OK
|
|
? Result.Fail<string>(CreateError.Create($"Failed to upload {fileName} to S3."))
|
|
: Result.Ok(fileKey);
|
|
}));
|
|
|
|
public virtual async Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) =>
|
|
await Result.Try((Func<Task<Result>>)(async () =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(BucketName))
|
|
return Result.Fail(NotFoundError.Create("Bucket name is not configured."));
|
|
|
|
var response = await Client.DeleteObjectAsync(new DeleteObjectRequest
|
|
{
|
|
BucketName = BucketName,
|
|
Key = fileKey
|
|
}, cancellationToken);
|
|
|
|
return Result.FailIf(response.HttpStatusCode == HttpStatusCode.NoContent,
|
|
UpdateError.Create($"Failed to delete {fileKey} from S3."));
|
|
}));
|
|
} |