Files
Musify/backend/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs
T
Admin b3ff1b6516 fix(infra): repair TransferFiles compensation + tidy MassTransit consistency
Fix (recoverability bug):
- TransferFilesToBucketActivity returned Completed() with no log, so its
  compensation could never delete the transferred files (orphaned objects on
  fault). IStorageService.TransferFilesAsync now returns the real uploaded
  keys; the activity logs them so Compensate can remove them. Also drops the
  Path.Combine key recomputation (produced backslash keys on Windows).

Consistency / DRY:
- Normalize all courier activities to internal + folder-based namespaces
  (several were public and in the flat .Activities namespace)
- Extract the duplicated exponential retry into UseStandardRetry
- Unify endpoint-name logic into a single EndpointHelper (remove the parallel
  MassTransitEndpointNames helper)

Build: 0 warnings, 0 errors.
2026-07-05 04:58:27 +02:00

61 lines
2.6 KiB
C#

using MassTransit;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
{
internal class UploadFileToBucketActivity(IStorageService storageHandler,
ILogger<UploadFileToBucketActivity> logger)
: IActivity<UploadFileToBucketArguments, UploadFileToBucketLog>
{
public const string ExecuteEndpointName = "upload-file-to-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<UploadFileToBucketArguments> executeContext)
{
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.FilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var fileName = Path.GetFileName(sourceFilePath);
var destinationKey = string.Join('/', new[] { executeContext.Arguments.DestinationRoute, fileName }
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\')));
try
{
await storageHandler.UploadFileAsync(
sourceFilePath,
executeContext.Arguments.DestinationBucket,
destinationKey,
executeContext.CancellationToken);
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
executeContext.Arguments.DestinationBucket,
destinationKey));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to upload file to bucket");
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UploadFileToBucketLog> compensateContext)
{
try
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey,
compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Error compensating file upload for {Bucket}/{Key}",
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey);
return compensateContext.Failed(exception);
}
}
}
}