using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain.Errors; namespace PostFundManagement.Application.Abstractions; public abstract class S3ServiceBase(IAmazonS3 amazonS3) { protected readonly IAmazonS3 Client = amazonS3; protected abstract string BucketName { get; } protected abstract string CdnBaseUrl { get; } public virtual async ValueTask> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { if (string.IsNullOrWhiteSpace(BucketName)) return Result.Fail(Domain.Errors.ValidationError.Create("Bucket name is not configured.")); if (string.IsNullOrWhiteSpace(CdnBaseUrl)) return Result.Fail(Domain.Errors.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(CreateError.Create("Failed to compute file hash.")); var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}"; 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(CreateError.Create($"Failed to upload {fileName} to S3.")) : Result.Ok($"{CdnBaseUrl}/{fileKey}"); })); public virtual async ValueTask DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) => await Result.Try((Func>)(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.")); })); }