A lot of changes

This commit is contained in:
2026-05-14 14:32:19 +02:00
parent d2b83524cf
commit 7215eebb5a
43 changed files with 2941 additions and 444 deletions
@@ -11,9 +11,13 @@ using Musify.Domain.Entities;
namespace Musify.Application.Tracks.Handler
{
public class CreateTrackCommandHandler(IDatabase database, IEventBus eventBus, IStorageService storageHandler,
ILogger<CreateTrackCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration, TrackConfiguration trackConfiguration)
: ICommandHandler<CreateTrackCommand, Result<TrackApplicationResponse> >
public class CreateTrackCommandHandler(
IDatabase database,
IEventBus eventBus,
ILogger<CreateTrackCommandHandler> logger,
ApplicationStorageConfiguration storageConfiguration,
TrackConfiguration trackConfiguration)
: ICommandHandler<CreateTrackCommand, Result<TrackApplicationResponse>>
{
public async ValueTask<Result<TrackApplicationResponse>> Handle(CreateTrackCommand request, CancellationToken cancellationToken)
{
@@ -25,102 +29,50 @@ namespace Musify.Application.Tracks.Handler
return Result.NotFound($"User {request.UserId} not found");
}
var trackEntity = CreateTrackCommand.ToEntity(
request,
trackConfiguration.Routes.PresetSmallPicture,
trackConfiguration.Routes.PresetMediumPicture,
trackConfiguration.Routes.PresetLargePicture);
var trackEntity = new Track
{
Title = request.Title,
NormalizedTitle = request.Title.ToUpperInvariant(),
OriginalPictureName = request.OriginalPictureName,
OriginalAudioName = request.OriginalAudioName,
SmallPictureName = trackConfiguration.Routes.PresetSmallPicture,
MediumPictureName = trackConfiguration.Routes.PresetMediumPicture,
LargePictureName = trackConfiguration.Routes.PresetLargePicture,
PicturesProcessingStatus = ProcessingStatus.Pending,
AudioTranscodeProcessingStatus = ProcessingStatus.Pending
};
var fullPictureKey = trackConfiguration.Routes.BuildOriginalPicturePath(trackEntity.OriginalPictureName);
var fullAudioKey = trackConfiguration.Routes.BuildOriginalAudioPath(trackEntity.OriginalAudioName);
await database.Tracks.AddAsync(trackEntity, cancellationToken);
var uploadResult = await UploadFilesAsync(request, fullPictureKey, fullAudioKey, cancellationToken);
if (!uploadResult.IsSuccess)
return uploadResult;
var publishPictureResult = await PublishUpdateEvent(request.UserId, trackEntity, cancellationToken);
if (!publishPictureResult.IsSuccess)
return Result.Error($"Failed to create track for {request.Title}");
var publishTranscodeResult = await PublishTranscodeEvent(request.UserId, trackEntity, cancellationToken);
if (!publishTranscodeResult.IsSuccess)
return Result.Error($"Failed to create track for {request.Title}");
try
{
await database.Tracks.AddAsync(trackEntity, cancellationToken);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to save track {Title} after uploading files", request.Title);
await RollbackFilesAsync(fullPictureKey, fullAudioKey, cancellationToken);
return Result.Error($"Failed to create track for {request.Title}");
}
var publishPictureResult = await PublishUpdateEvent(trackEntity, cancellationToken);
if (!publishPictureResult.IsSuccess)
{
await RollbackTrackAfterPublishFailureAsync(trackEntity, fullPictureKey, fullAudioKey, cancellationToken);
return Result.Error($"Failed to create track for {request.Title}");
}
var publishTranscodeResult = await PublishTranscodeEvent(trackEntity, cancellationToken);
if (!publishTranscodeResult.IsSuccess)
{
await RollbackTrackAfterPublishFailureAsync(trackEntity, fullPictureKey, fullAudioKey, cancellationToken);
logger.LogError(exception, "Failed to save track {Title}", request.Title);
return Result.Error($"Failed to create track for {request.Title}");
}
return Result.Created(TrackApplicationResponse.FromEntity(trackEntity));
}
async Task<Result> UploadFilesAsync(
CreateTrackCommand request,
string pictureKey,
string audioKey,
CancellationToken cancellationToken)
{
try
{
await storageHandler.UploadFileAsync(
request.Picture.FileStream,
request.Picture.ContentType,
storageConfiguration.Bucket,
pictureKey,
cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to upload picture for track {Title}", request.Title);
return Result.Error($"Failed to upload picture for {request.Title}");
}
try
{
await storageHandler.UploadFileAsync(
request.Audio.FileStream,
request.Audio.ContentType,
storageConfiguration.Bucket,
audioKey,
cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to upload audio for track {Title}", request.Title);
await RollbackFilesAsync(pictureKey, audioKey, cancellationToken);
return Result.Error($"Failed to upload audio for {request.Title}");
}
return Result.Success();
}
async Task RollbackFilesAsync(string pictureKey, string audioKey, CancellationToken cancellationToken)
{
await storageHandler.RemoveFileAsync(storageConfiguration.Bucket, pictureKey, cancellationToken);
await storageHandler.RemoveFileAsync(storageConfiguration.Bucket, audioKey, cancellationToken);
logger.LogInformation("Rolled back uploaded files {PictureKey}, {AudioKey}", pictureKey, audioKey);
}
async Task<Result> PublishUpdateEvent(Track track, CancellationToken cancellationToken)
async Task<Result> PublishUpdateEvent(Guid userId, Track track, CancellationToken cancellationToken)
{
try
{
await eventBus.PublishAsync(new UpdateTrackPictureEvent(
track.Id,
storageConfiguration.Bucket,
trackConfiguration.Routes.BuildOriginalPicturePath(track.OriginalPictureName),
trackConfiguration.Routes.BuildOriginalPicturePath(userId, track.OriginalPictureName),
new ImageSize(
trackConfiguration.Routes.SmallPicturesPath,
trackConfiguration.PicturesSizes.SmallPictureWidth,
@@ -142,7 +94,7 @@ namespace Musify.Application.Tracks.Handler
}
}
async Task<Result> PublishTranscodeEvent(Track track, CancellationToken cancellationToken)
async Task<Result> PublishTranscodeEvent(Guid userId, Track track, CancellationToken cancellationToken)
{
var destinationFolderAudio = trackConfiguration.Routes.BuildProcessedAudioPath(Guid.NewGuid().ToString());
@@ -151,7 +103,7 @@ namespace Musify.Application.Tracks.Handler
await eventBus.PublishAsync(new UpdateTrackAudioEvent(
track.Id,
storageConfiguration.Bucket,
trackConfiguration.Routes.BuildOriginalAudioPath(track.OriginalAudioName),
trackConfiguration.Routes.BuildOriginalAudioPath(userId, track.OriginalAudioName),
storageConfiguration.Bucket,
destinationFolderAudio), cancellationToken);
return Result.Success();
@@ -163,30 +115,5 @@ namespace Musify.Application.Tracks.Handler
}
}
async Task RollbackTrackAfterPublishFailureAsync(
Track track,
string pictureKey,
string audioKey,
CancellationToken cancellationToken)
{
try
{
database.Tracks.Remove(track);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback track {TrackId} persistence after publish failure", track.Id);
}
try
{
await RollbackFilesAsync(pictureKey, audioKey, cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback uploaded files after publish failure for track {TrackId}", track.Id);
}
}
}
}