diff --git a/backend/Core/Musify.Application/Contracts/IDatabase.cs b/backend/Core/Musify.Application/Contracts/IDatabase.cs index 5ec518a..89e0964 100644 --- a/backend/Core/Musify.Application/Contracts/IDatabase.cs +++ b/backend/Core/Musify.Application/Contracts/IDatabase.cs @@ -34,6 +34,8 @@ public interface IDatabase public Task BeginTransactionAsync(System.Data.IsolationLevel isolationLevel, CancellationToken cancellationToken); + public DbSet Set() where TEntity : class; + public Task SaveChangesAsync(CancellationToken cancellationToken); public Task> TrySaveChangesAsync(Error onUniqueViolation, CancellationToken cancellationToken); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/DeleteAlbumFromDbActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/DeleteAlbumFromDbActivity.cs deleted file mode 100644 index 37baf24..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/DeleteAlbumFromDbActivity.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Albums; - -internal class DeleteAlbumFromDbActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "delete-album-from-db"; - - public async Task Execute(ExecuteContext executeContext) - { - var album = await database.Albums - .SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken); - - if (album == null) - { - logger.LogInformation( - "Album {AlbumId} not found while deleting from DB, skipping", - executeContext.Arguments.AlbumId); - return executeContext.Completed(); - } - - database.Albums.Remove(album); - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Deleted album {AlbumId} from DB", album.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsFailedActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsFailedActivity.cs deleted file mode 100644 index c75652f..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsFailedActivity.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Albums; - -internal class MarkAlbumAsFailedActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-album-as-failed"; - - public async Task Execute(ExecuteContext executeContext) - { - var album = await database.Albums - .SingleOrDefaultAsync(a => a.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken); - - if (album != null) - { - album.LifeCycleStatus = LifeCycleStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - logger.LogInformation("Marked album {AlbumId} as failed", executeContext.Arguments.AlbumId); - } - - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsRemovingActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsRemovingActivity.cs deleted file mode 100644 index edec538..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Albums/MarkAlbumAsRemovingActivity.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Albums; - -internal class MarkAlbumAsRemovingActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-album-as-removing"; - - public async Task Execute(ExecuteContext executeContext) - { - var album = await database.Albums - .SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken); - - if (album == null) - { - logger.LogInformation( - "Album {AlbumId} not found while marking as removing, skipping", - executeContext.Arguments.AlbumId); - return executeContext.Completed(); - } - - album.LifeCycleStatus = LifeCycleStatus.Removing; - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Marked album {AlbumId} as removing", album.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs index edef0a9..9b38fb0 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs @@ -1,10 +1,9 @@ using MassTransit; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Musify.Application.Contracts; -using Musify.Domain.Entities; using Musify.Domain.ValueObjects; using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Activities.Audio; @@ -17,26 +16,15 @@ internal class GenerateAudioWorkflowPathsActivity( public async Task Execute(ExecuteContext executeContext) { - Track track; - try - { - var getTrack = await database.Tracks.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found."); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to retrieve track for TrackId {TrackId}", - executeContext.Arguments.TrackId); - throw; - } + var arguments = executeContext.Arguments; + var track = await TrackAudioStatus.LoadAsync(database, arguments.TrackId, executeContext.CancellationToken); try { track.Audio.TranscodeStatus = ProcessingStatus.Processing; - var folderName = Guid.NewGuid().ToString(); - var sourceFileName = Path.GetFileName(executeContext.Arguments.SourceKey); - var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, folderName); - var sourceFilePath = Path.Combine(workingDirectory, sourceFileName); + + var workingDirectory = Path.Combine(arguments.TemporaryRootDirectory, Guid.NewGuid().ToString()); + var sourceFilePath = Path.Combine(workingDirectory, Path.GetFileName(arguments.SourceKey)); Directory.CreateDirectory(workingDirectory); await database.SaveChangesAsync(executeContext.CancellationToken); @@ -50,22 +38,9 @@ internal class GenerateAudioWorkflowPathsActivity( [RoutingSlipVariableNames.Audio.TranscodedDirectory] = workingDirectory }); } - catch (Exception exception) + catch { - logger.LogError(exception, "Failed to generate audio workflow paths for {SourceKey}", - executeContext.Arguments.SourceKey); - - try - { - track.Audio.TranscodeStatus = ProcessingStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - } - catch (Exception dbException) - { - logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}", - executeContext.Arguments.TrackId); - } - + await TrackAudioStatus.MarkFailedAsync(database, arguments.TrackId, executeContext.CancellationToken); throw; } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TrackAudioStatus.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TrackAudioStatus.cs new file mode 100644 index 0000000..7f40abc --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TrackAudioStatus.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore; +using Musify.Application.Contracts; +using Musify.Domain.Entities; +using Musify.Domain.ValueObjects; + +namespace Musify.Infrastructure.MassTransit.Activities.Audio; + +/// Shared bookkeeping of the audio processing activities. +internal static class TrackAudioStatus +{ + public static async Task LoadAsync(IDatabase database, Guid trackId, CancellationToken cancellationToken) => + await database.Tracks.SingleOrDefaultAsync(track => track.Id == trackId, cancellationToken) + ?? throw new InvalidOperationException($"Track with id {trackId} not found"); + + public static async Task MarkFailedAsync(IDatabase database, Guid trackId, CancellationToken cancellationToken) + { + var track = await database.Tracks.SingleOrDefaultAsync(t => t.Id == trackId, cancellationToken); + if (track == null) + return; + + track.Audio.TranscodeStatus = ProcessingStatus.Failed; + await database.SaveChangesAsync(cancellationToken); + } +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TranscodeAudioActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TranscodeAudioActivity.cs index 2c674b6..ee7e5d1 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TranscodeAudioActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/TranscodeAudioActivity.cs @@ -1,11 +1,9 @@ using MassTransit; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Musify.Application.Contracts; -using Musify.Domain.Entities; -using Musify.Domain.ValueObjects; using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Logs; +using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Activities.Audio; @@ -19,22 +17,13 @@ internal class TranscodeAudioActivity( public async Task Execute(ExecuteContext executeContext) { - var sourceFilePath = executeContext.GetVariable(executeContext.Arguments.SourceFilePathVariable); - ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath)); - var workingDirectory = executeContext.GetVariable(executeContext.Arguments.WorkingDirectoryVariable); - ArgumentNullException.ThrowIfNull(workingDirectory, nameof(workingDirectory)); + var arguments = executeContext.Arguments; + var sourceFilePath = executeContext.GetVariable(arguments.SourceFilePathVariable); + ArgumentNullException.ThrowIfNull(sourceFilePath); + var workingDirectory = executeContext.GetVariable(arguments.WorkingDirectoryVariable); + ArgumentNullException.ThrowIfNull(workingDirectory); - Track track; - try - { - var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - track = getTrack ?? throw new InvalidOperationException($"Track with Id {executeContext.Arguments.TrackId} not found"); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to retrieve track with Id {TrackId} from database", executeContext.Arguments.TrackId); - throw; - } + await TrackAudioStatus.LoadAsync(database, arguments.TrackId, executeContext.CancellationToken); try { @@ -42,9 +31,7 @@ internal class TranscodeAudioActivity( await using (var fileStream = File.OpenRead(sourceFilePath)) { transcodeResult = await audioTranscoder.TranscodeToAudioFileAsync( - fileStream, - workingDirectory, - executeContext.CancellationToken); + fileStream, workingDirectory, executeContext.CancellationToken); } if (transcodeResult.StatusCode != 0) @@ -52,51 +39,27 @@ internal class TranscodeAudioActivity( File.Delete(sourceFilePath); - var log = new TranscodeAudioLog(workingDirectory); - return executeContext.CompletedWithVariables(log, new Dictionary + return executeContext.CompletedWithVariables(new TranscodeAudioLog(workingDirectory), new Dictionary { [RoutingSlipVariableNames.Audio.DurationSeconds] = transcodeResult.Duration.TotalSeconds }); } - catch (Exception exception) + catch { - logger.LogError(exception, "Failed to transcode {SourceFilePath}", sourceFilePath); - - try - { - track.Audio.TranscodeStatus = ProcessingStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - } - catch (Exception dbException) - { - logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}", - executeContext.Arguments.TrackId); - } - + await TrackAudioStatus.MarkFailedAsync(database, arguments.TrackId, executeContext.CancellationToken); throw; } } public Task Compensate(CompensateContext compensateContext) { - try - { - if (Directory.Exists(compensateContext.Log.WorkingDirectory)) - { - Directory.Delete(compensateContext.Log.WorkingDirectory, recursive: true); - } - else - { - logger.LogWarning("Working directory {WorkingDirectory} not found during compensation", - compensateContext.Log.WorkingDirectory); - } + var workingDirectory = compensateContext.Log.WorkingDirectory; - return Task.FromResult(compensateContext.Compensated()); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to compensate transcoded audio directory {WorkingDirectory}", compensateContext.Log.WorkingDirectory); - return Task.FromResult(compensateContext.Failed(exception)); - } + if (Directory.Exists(workingDirectory)) + Directory.Delete(workingDirectory, recursive: true); + else + logger.LogWarning("Working directory {WorkingDirectory} not found during compensation", workingDirectory); + + return Task.FromResult(compensateContext.Compensated()); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/UpdateTrackAudioActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/UpdateTrackAudioActivity.cs index ef79453..d683d09 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/UpdateTrackAudioActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Audio/UpdateTrackAudioActivity.cs @@ -1,9 +1,7 @@ using MassTransit; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Musify.Application.Contracts; using Musify.Application.Events; -using Musify.Domain.Entities; using Musify.Domain.ValueObjects; using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Logs; @@ -20,92 +18,49 @@ internal class UpdateTrackAudioActivity( public async Task Execute(ExecuteContext executeContext) { - Track track; - try - { - var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found"); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to retrieve track {TrackId}", - executeContext.Arguments.TrackId); - throw; - } - - var audioFolderKey = executeContext.Arguments.AudioFolderKey; - logger.LogDebug("Resolved audio folder key: {AudioFolderKey}", audioFolderKey); - - if (string.IsNullOrWhiteSpace(audioFolderKey)) - { - throw new InvalidOperationException($"Audio folder key is empty for track {track.Id}"); - } + var arguments = executeContext.Arguments; + var track = await TrackAudioStatus.LoadAsync(database, arguments.TrackId, executeContext.CancellationToken); try { - var durationSeconds = executeContext.GetVariable(executeContext.Arguments.DurationSecondsVariable) - ?? throw new InvalidOperationException($"Missing routing slip variable {executeContext.Arguments.DurationSecondsVariable}"); + if (string.IsNullOrWhiteSpace(arguments.AudioFolderKey)) + throw new InvalidOperationException($"Audio folder key is empty for track {track.Id}"); - var log = new UpdateTrackAudioLog( - track.Id, - track.Audio.FolderName, - track.DurationSeconds); + var durationSeconds = executeContext.GetVariable(arguments.DurationSecondsVariable) + ?? throw new InvalidOperationException($"Missing routing slip variable {arguments.DurationSecondsVariable}"); - track.Audio.FolderName = Path.GetFileName(audioFolderKey); + var log = new UpdateTrackAudioLog(track.Id, track.Audio.FolderName, track.DurationSeconds); + + track.Audio.FolderName = Path.GetFileName(arguments.AudioFolderKey); track.DurationSeconds = durationSeconds; track.Audio.TranscodeStatus = ProcessingStatus.Completed; await database.SaveChangesAsync(executeContext.CancellationToken); - logger.LogInformation("Updated track {TrackId} audio", executeContext.Arguments.TrackId); + logger.LogInformation("Updated track {TrackId} audio", arguments.TrackId); - await publishEndpoint.Publish( - new TrackAudioProcessed(executeContext.Arguments.TrackId), - executeContext.CancellationToken); + await publishEndpoint.Publish(new TrackAudioProcessed(arguments.TrackId), executeContext.CancellationToken); return executeContext.Completed(log); } - catch (Exception exception) + catch { - logger.LogError(exception, "Failed to update track {TrackId} audio", - executeContext.Arguments.TrackId); - - try - { - track.Audio.TranscodeStatus = ProcessingStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - } - catch (Exception dbException) - { - logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}", - executeContext.Arguments.TrackId); - } - + await TrackAudioStatus.MarkFailedAsync(database, arguments.TrackId, executeContext.CancellationToken); throw; } } public async Task Compensate(CompensateContext compensateContext) { - try - { - var track = await database.Tracks.SingleOrDefaultAsync(t => t.Id == compensateContext.Log.TrackId, compensateContext.CancellationToken); - if (track == null) - throw new InvalidOperationException($"Track with id {compensateContext.Log.TrackId} not found"); + var log = compensateContext.Log; - track.Audio.FolderName = compensateContext.Log.PreviousAudioFolderName; - track.DurationSeconds = compensateContext.Log.PreviousDurationSeconds; - track.Audio.TranscodeStatus = ProcessingStatus.Failed; - await database.SaveChangesAsync(compensateContext.CancellationToken); + var track = await TrackAudioStatus.LoadAsync(database, log.TrackId, compensateContext.CancellationToken); + track.Audio.FolderName = log.PreviousAudioFolderName; + track.DurationSeconds = log.PreviousDurationSeconds; + track.Audio.TranscodeStatus = ProcessingStatus.Failed; + await database.SaveChangesAsync(compensateContext.CancellationToken); - logger.LogInformation("Compensated track {TrackId} audio", compensateContext.Log.TrackId); + logger.LogInformation("Compensated track {TrackId} audio", log.TrackId); - return compensateContext.Compensated(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to compensate track {TrackId} audio", - compensateContext.Log.TrackId); - return compensateContext.Failed(exception); - } + return compensateContext.Compensated(); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs index 727b65c..b6e4861 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs @@ -7,7 +7,7 @@ using Musify.Infrastructure.MassTransit.Logs; namespace Musify.Infrastructure.MassTransit.Activities.Files; internal class CopyFileInBucketActivity( - IStorageService storageHandler, + IStorageService storageService, ILogger logger) : IActivity { @@ -17,7 +17,7 @@ internal class CopyFileInBucketActivity( { try { - await storageHandler.CopyFileAsync( + await storageService.CopyFileAsync( executeContext.Arguments.SourceBucket, executeContext.Arguments.SourceKey, executeContext.Arguments.DestinationBucket, @@ -43,7 +43,7 @@ internal class CopyFileInBucketActivity( { try { - await storageHandler.RemoveFileAsync( + await storageService.RemoveFileAsync( compensateContext.Log.DestinationBucket, compensateContext.Log.DestinationKey, compensateContext.CancellationToken); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs index b59c525..6b246a5 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs @@ -7,7 +7,7 @@ using Musify.Infrastructure.MassTransit.Logs; namespace Musify.Infrastructure.MassTransit.Activities.Files; internal class DownloadFileFromBucketActivity( - IStorageService storageHandler, + IStorageService storageService, ILogger logger) : IActivity { @@ -16,11 +16,11 @@ internal class DownloadFileFromBucketActivity( public async Task Execute(ExecuteContext executeContext) { var destinationPath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariable); - ArgumentNullException.ThrowIfNull(destinationPath, nameof(destinationPath)); + ArgumentNullException.ThrowIfNull(destinationPath); try { - using var fileStream = await storageHandler.GetFileAsync( + using var fileStream = await storageService.GetFileAsync( executeContext.Arguments.Bucket, executeContext.Arguments.Key, executeContext.CancellationToken) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs index f76808d..73c737b 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs @@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Arguments; namespace Musify.Infrastructure.MassTransit.Activities.Files; internal class RemoveFileFromBucketActivity( - IStorageService storageHandler, + IStorageService storageService, ILogger logger) : IExecuteActivity { @@ -18,7 +18,7 @@ internal class RemoveFileFromBucketActivity( { try { - await storageHandler.RemoveFileAsync( + await storageService.RemoveFileAsync( executeContext.Arguments.Bucket, executeContext.Arguments.Key, executeContext.CancellationToken); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs index fbf5e19..eb397d8 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs @@ -7,7 +7,7 @@ using Musify.Infrastructure.MassTransit.Logs; namespace Musify.Infrastructure.MassTransit.Activities.Files; internal class TransferFilesToBucketActivity( - IStorageService storageHandler, + IStorageService storageService, ILogger logger) : IActivity { @@ -16,13 +16,13 @@ internal class TransferFilesToBucketActivity( public async Task Execute(ExecuteContext executeContext) { var folderPath = executeContext.GetVariable(executeContext.Arguments.SourceDirectoryVariable); - ArgumentNullException.ThrowIfNull(folderPath, nameof(folderPath)); + ArgumentNullException.ThrowIfNull(folderPath); var destinationKey = executeContext.Arguments.DestinationKey; - ArgumentNullException.ThrowIfNull(destinationKey, nameof(destinationKey)); + ArgumentNullException.ThrowIfNull(destinationKey); try { - var uploadedKeys = await storageHandler.TransferFilesAsync( + var uploadedKeys = await storageService.TransferFilesAsync( folderPath, executeContext.Arguments.DestinationBucket, destinationKey, @@ -47,7 +47,7 @@ internal class TransferFilesToBucketActivity( { foreach (var uploadedKey in compensateContext.Log.UploadedKeys) { - await storageHandler.RemoveFileAsync( + await storageService.RemoveFileAsync( compensateContext.Log.DestinationBucket, uploadedKey, compensateContext.CancellationToken); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs index 3f8fed5..c9adc6f 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs @@ -6,7 +6,7 @@ using Musify.Infrastructure.MassTransit.Logs; namespace Musify.Infrastructure.MassTransit.Activities.Files; -internal class UploadFileToBucketActivity(IStorageService storageHandler, +internal class UploadFileToBucketActivity(IStorageService storageService, ILogger logger) : IActivity { @@ -15,7 +15,7 @@ internal class UploadFileToBucketActivity(IStorageService storageHandler, public async Task Execute(ExecuteContext executeContext) { var sourceFilePath = executeContext.GetVariable(executeContext.Arguments.FilePathVariable); - ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath)); + ArgumentNullException.ThrowIfNull(sourceFilePath); var fileName = Path.GetFileName(sourceFilePath); var destinationKey = string.Join('/', new[] { executeContext.Arguments.DestinationRoute, fileName } .Where(static s => !string.IsNullOrWhiteSpace(s)) @@ -23,7 +23,7 @@ internal class UploadFileToBucketActivity(IStorageService storageHandler, try { - await storageHandler.UploadFileAsync( + await storageService.UploadFileAsync( sourceFilePath, executeContext.Arguments.DestinationBucket, destinationKey, @@ -43,7 +43,7 @@ internal class UploadFileToBucketActivity(IStorageService storageHandler, { try { - await storageHandler.RemoveFileAsync( + await storageService.RemoveFileAsync( compensateContext.Log.DestinationBucket, compensateContext.Log.DestinationKey, compensateContext.CancellationToken); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/LifeCycle/LifeCycleActivities.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/LifeCycle/LifeCycleActivities.cs new file mode 100644 index 0000000..a69d39e --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/LifeCycle/LifeCycleActivities.cs @@ -0,0 +1,97 @@ +using MassTransit; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Musify.Application.Contracts; +using Musify.Domain.Abstractions; +using Musify.Domain.Entities; +using Musify.Infrastructure.MassTransit.Arguments; + +namespace Musify.Infrastructure.MassTransit.Activities.LifeCycle; + +/// Sets the life-cycle status of an entity (removing, failed, ...). A missing entity is skipped. +internal abstract class MarkLifeCycleActivity(IDatabase database, ILogger logger) + : IExecuteActivity + where TEntity : class, IHasLifeCycle +{ + public async Task Execute(ExecuteContext executeContext) + { + var arguments = executeContext.Arguments; + var entity = await database.Set() + .SingleOrDefaultAsync(e => e.Id == arguments.Id, executeContext.CancellationToken); + + if (entity == null) + { + logger.LogInformation("{Entity} {Id} not found while marking it as {Status}, skipping", typeof(TEntity).Name, arguments.Id, arguments.Status); + return executeContext.Completed(); + } + + entity.LifeCycleStatus = arguments.Status; + await database.SaveChangesAsync(executeContext.CancellationToken); + + logger.LogInformation("Marked {Entity} {Id} as {Status}", typeof(TEntity).Name, arguments.Id, arguments.Status); + return executeContext.Completed(); + } +} + +/// Deletes an entity row. A missing entity is skipped. +internal abstract class DeleteEntityActivity(IDatabase database, ILogger logger) + : IExecuteActivity + where TEntity : class, IHasLifeCycle +{ + public async Task Execute(ExecuteContext executeContext) + { + var id = executeContext.Arguments.Id; + var entity = await database.Set() + .SingleOrDefaultAsync(e => e.Id == id, executeContext.CancellationToken); + + if (entity == null) + { + logger.LogInformation("{Entity} {Id} not found while deleting it, skipping", typeof(TEntity).Name, id); + return executeContext.Completed(); + } + + database.Set().Remove(entity); + await database.SaveChangesAsync(executeContext.CancellationToken); + + logger.LogInformation("Deleted {Entity} {Id} from the database", typeof(TEntity).Name, id); + return executeContext.Completed(); + } +} + +// One concrete activity per entity keeps a separate queue for each of them: +// the endpoint name is the kebab-cased class name. +internal sealed class MarkTrackLifeCycleActivity(IDatabase database, ILogger logger) + : MarkLifeCycleActivity(database, logger) +{ + public const string ExecuteEndpointName = "mark-track-life-cycle"; +} + +internal sealed class MarkPlayListLifeCycleActivity(IDatabase database, ILogger logger) + : MarkLifeCycleActivity(database, logger) +{ + public const string ExecuteEndpointName = "mark-play-list-life-cycle"; +} + +internal sealed class MarkAlbumLifeCycleActivity(IDatabase database, ILogger logger) + : MarkLifeCycleActivity(database, logger) +{ + public const string ExecuteEndpointName = "mark-album-life-cycle"; +} + +internal sealed class DeleteTrackActivity(IDatabase database, ILogger logger) + : DeleteEntityActivity(database, logger) +{ + public const string ExecuteEndpointName = "delete-track"; +} + +internal sealed class DeletePlayListActivity(IDatabase database, ILogger logger) + : DeleteEntityActivity(database, logger) +{ + public const string ExecuteEndpointName = "delete-play-list"; +} + +internal sealed class DeleteAlbumActivity(IDatabase database, ILogger logger) + : DeleteEntityActivity(database, logger) +{ + public const string ExecuteEndpointName = "delete-album"; +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs index 37dfe63..48305af 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs @@ -1,6 +1,7 @@ using MassTransit; using Microsoft.Extensions.Logging; using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Activities.Pictures; @@ -12,39 +13,27 @@ internal class GeneratePictureWorkflowPathsActivity( internal const string ResizedPictureFileExtension = ".webp"; - public async Task Execute(ExecuteContext executeContext) + public Task Execute(ExecuteContext executeContext) { - try + var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, Guid.NewGuid().ToString()); + + var sourceFilePath = Path.Combine( + workingDirectory, Guid.NewGuid().ToString() + Path.GetExtension(executeContext.Arguments.SourceKey)); + var smallPictureFilePath = Path.Combine(workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); + var mediumPictureFilePath = Path.Combine(workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); + var largePictureFilePath = Path.Combine(workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); + + Directory.CreateDirectory(workingDirectory); + + logger.LogDebug("Generated picture workflow paths in {WorkingDirectory}", workingDirectory); + + return Task.FromResult(executeContext.CompletedWithVariables(new Dictionary { - var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, Guid.NewGuid().ToString()); - - var sourceFilePath = Path.Combine( - workingDirectory, Guid.NewGuid().ToString() + Path.GetExtension(executeContext.Arguments.SourceKey)); - var smallPictureFilePath = Path.Combine( - workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); - var mediumPictureFilePath = Path.Combine( - workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); - var largePictureFilePath = Path.Combine( - workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension); - - Directory.CreateDirectory(workingDirectory); - - logger.LogDebug("Generated picture workflow paths in {WorkingDirectory}", - workingDirectory); - return executeContext.CompletedWithVariables(new Dictionary - { - [RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory, - [RoutingSlipVariableNames.Picture.OriginalFilePath] = sourceFilePath, - [RoutingSlipVariableNames.Picture.SmallResizedFilePath] = smallPictureFilePath, - [RoutingSlipVariableNames.Picture.MediumResizedFilePath] = mediumPictureFilePath, - [RoutingSlipVariableNames.Picture.LargeResizedFilePath] = largePictureFilePath - }); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to generate picture workflow paths for {SourceKey}", - executeContext.Arguments.SourceKey); - throw; - } + [RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory, + [RoutingSlipVariableNames.Picture.OriginalFilePath] = sourceFilePath, + [RoutingSlipVariableNames.Picture.SmallResizedFilePath] = smallPictureFilePath, + [RoutingSlipVariableNames.Picture.MediumResizedFilePath] = mediumPictureFilePath, + [RoutingSlipVariableNames.Picture.LargeResizedFilePath] = largePictureFilePath + })); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs index dee6d73..f3614ba 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs @@ -16,9 +16,9 @@ internal class ResizePictureActivity( public async Task Execute(ExecuteContext executeContext) { var sourceFilePath = executeContext.GetVariable(executeContext.Arguments.SourceFilePathVariable); - ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath)); + ArgumentNullException.ThrowIfNull(sourceFilePath); var destinationFilePath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariable); - ArgumentNullException.ThrowIfNull(destinationFilePath, nameof(destinationFilePath)); + ArgumentNullException.ThrowIfNull(destinationFilePath); try { diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateAlbumPictureActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateAlbumPictureActivity.cs deleted file mode 100644 index 9b5d1c1..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateAlbumPictureActivity.cs +++ /dev/null @@ -1,110 +0,0 @@ -using MassTransit; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Application.Events; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Logs; - -namespace Musify.Infrastructure.MassTransit.Activities.Pictures; - -internal class UpdateAlbumPictureActivity( - IDatabase database, - IPublishEndpoint publishEndpoint, - ILogger logger) - : IActivity -{ - public const string ExecuteEndpointName = "update-album-picture"; - - public async Task Execute(ExecuteContext executeContext) - { - var smallResizedVariable = executeContext.GetVariable(executeContext.Arguments.SmallPictureVariable); - ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable)); - var mediumResizedVariable = executeContext.GetVariable(executeContext.Arguments.MediumPictureVariable); - ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable)); - var largeResizedVariable = executeContext.GetVariable(executeContext.Arguments.LargePictureVariable); - ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable)); - - try - { - var album = await database.Albums.FindAsync( - [executeContext.Arguments.AlbumId], - cancellationToken: executeContext.CancellationToken); - - if (album == null) - { - logger.LogWarning("Album {AlbumId} not found", - executeContext.Arguments.AlbumId); - throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} not found"); - } - - if (album.Pictures == null) - { - logger.LogWarning("Album {AlbumId} has no pending picture upload", - executeContext.Arguments.AlbumId); - throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} has no pending picture upload"); - } - - var log = new UpdateAlbumPictureLog( - album.Id, - album.Pictures.OriginalName, - album.Pictures.SmallName, - album.Pictures.MediumName, - album.Pictures.LargeName); - - album.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey); - album.Pictures.SmallName = Path.GetFileName(smallResizedVariable); - album.Pictures.MediumName = Path.GetFileName(mediumResizedVariable); - album.Pictures.LargeName = Path.GetFileName(largeResizedVariable); - - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Updated album {AlbumId} pictures", - executeContext.Arguments.AlbumId); - - await publishEndpoint.Publish( - new AlbumPictureProcessed(executeContext.Arguments.AlbumId), - executeContext.CancellationToken); - - return executeContext.Completed(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to update album {AlbumId} pictures", - executeContext.Arguments.AlbumId); - throw; - } - } - - public async Task Compensate(CompensateContext compensateContext) - { - try - { - var album = await database.Albums.FindAsync( - [compensateContext.Log.AlbumId], - cancellationToken: compensateContext.CancellationToken); - - if (album == null) - { - return compensateContext.Compensated(); - } - - album.Pictures = new EntityPictures - { - OriginalName = compensateContext.Log.PreviousOriginalPictureKey, - SmallName = compensateContext.Log.PreviousSmallPictureKey, - MediumName = compensateContext.Log.PreviousMediumPictureKey, - LargeName = compensateContext.Log.PreviousLargePictureKey - }; - - await database.SaveChangesAsync(compensateContext.CancellationToken); - - return compensateContext.Compensated(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to compensate album {AlbumId} pictures", compensateContext.Log.AlbumId); - return compensateContext.Failed(exception); - } - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePicturesActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePicturesActivity.cs new file mode 100644 index 0000000..76d84f3 --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePicturesActivity.cs @@ -0,0 +1,160 @@ +using MassTransit; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Musify.Application.Contracts; +using Musify.Application.Events; +using Musify.Domain.Entities; +using Musify.Domain.ValueObjects; +using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Logs; + +namespace Musify.Infrastructure.MassTransit.Activities.Pictures; + +internal sealed record PictureNames(string? Original, string? Small, string? Medium, string? Large); + +/// Stores the processed picture names on the owner and reports it; compensation restores the previous names. +internal abstract class UpdatePicturesActivity( + IDatabase database, + IPublishEndpoint publishEndpoint, + ILogger logger) + : IActivity + where TEntity : class +{ + protected IDatabase Database { get; } = database; + + protected abstract Task FindAsync(Guid id, CancellationToken cancellationToken); + + /// Reads the current names; throws when the entity has no pending picture. + protected abstract PictureNames Read(TEntity entity); + + protected abstract void Write(TEntity entity, PictureNames names, bool succeeded); + + protected abstract object ProcessedEvent(Guid id); + + public async Task Execute(ExecuteContext executeContext) + { + var arguments = executeContext.Arguments; + var small = RequireVariable(executeContext, arguments.SmallPictureVariable); + var medium = RequireVariable(executeContext, arguments.MediumPictureVariable); + var large = RequireVariable(executeContext, arguments.LargePictureVariable); + + var entity = await FindAsync(arguments.SubjectId, executeContext.CancellationToken) + ?? throw new InvalidOperationException($"{typeof(TEntity).Name} {arguments.SubjectId} not found"); + + var previous = Read(entity); + Write( + entity, + new PictureNames( + Path.GetFileName(arguments.OriginalPictureKey), + Path.GetFileName(small), + Path.GetFileName(medium), + Path.GetFileName(large)), + succeeded: true); + await Database.SaveChangesAsync(executeContext.CancellationToken); + + logger.LogInformation("Updated {Entity} {Id} pictures", typeof(TEntity).Name, arguments.SubjectId); + + await publishEndpoint.Publish(ProcessedEvent(arguments.SubjectId), executeContext.CancellationToken); + + return executeContext.Completed(new UpdatePicturesLog( + arguments.SubjectId, previous.Original, previous.Small, previous.Medium, previous.Large)); + } + + public async Task Compensate(CompensateContext compensateContext) + { + var log = compensateContext.Log; + + try + { + var entity = await FindAsync(log.SubjectId, compensateContext.CancellationToken); + if (entity == null) + return compensateContext.Compensated(); + + var previous = new PictureNames(log.PreviousOriginalName, log.PreviousSmallName, log.PreviousMediumName, log.PreviousLargeName); + Write(entity, previous, succeeded: false); + await Database.SaveChangesAsync(compensateContext.CancellationToken); + + return compensateContext.Compensated(); + } + catch (Exception exception) + { + logger.LogError(exception, "Failed to compensate {Entity} {Id} pictures", typeof(TEntity).Name, log.SubjectId); + return compensateContext.Failed(exception); + } + } + + protected static PictureNames ReadPictures(EntityPictures? pictures) => + pictures == null + ? throw new InvalidOperationException("The entity has no pending picture upload") + : new PictureNames(pictures.OriginalName, pictures.SmallName, pictures.MediumName, pictures.LargeName); + + protected static void WritePictures(EntityPictures? pictures, PictureNames names) + { + if (pictures == null) + return; + + pictures.OriginalName = names.Original ?? pictures.OriginalName; + pictures.SmallName = names.Small; + pictures.MediumName = names.Medium; + pictures.LargeName = names.Large; + } + + private static string RequireVariable(ExecuteContext executeContext, string variableName) => + executeContext.GetVariable(variableName) + ?? throw new InvalidOperationException($"Missing routing slip variable {variableName}"); +} + +internal sealed class UpdateTrackPictureActivity(IDatabase database, IPublishEndpoint publishEndpoint, ILogger logger) + : UpdatePicturesActivity(database, publishEndpoint, logger) +{ + public const string ExecuteEndpointName = "update-track-picture"; + + protected override Task FindAsync(Guid id, CancellationToken cancellationToken) => + Database.Tracks.SingleOrDefaultAsync(track => track.Id == id, cancellationToken); + + protected override PictureNames Read(Track track) => + new(track.Pictures.OriginalName, track.Pictures.SmallName, track.Pictures.MediumName, track.Pictures.LargeName); + + protected override void Write(Track track, PictureNames names, bool succeeded) + { + track.Pictures.OriginalName = names.Original; + track.Pictures.SmallName = names.Small; + track.Pictures.MediumName = names.Medium; + track.Pictures.LargeName = names.Large; + track.Pictures.ProcessingStatus = succeeded ? ProcessingStatus.Completed : ProcessingStatus.Failed; + } + + protected override object ProcessedEvent(Guid id) => new TrackPictureProcessed(id); +} + +internal sealed class UpdatePlayListPictureActivity(IDatabase database, IPublishEndpoint publishEndpoint, ILogger logger) + : UpdatePicturesActivity(database, publishEndpoint, logger) +{ + public const string ExecuteEndpointName = "update-play-list-picture"; + + protected override Task FindAsync(Guid id, CancellationToken cancellationToken) => + Database.PlayLists.SingleOrDefaultAsync(playList => playList.Id == id, cancellationToken); + + protected override PictureNames Read(PlayList playList) => ReadPictures(playList.Pictures); + + protected override void Write(PlayList playList, PictureNames names, bool succeeded) => + WritePictures(playList.Pictures, names); + + protected override object ProcessedEvent(Guid id) => new PlayListPictureProcessed(id); +} + +internal sealed class UpdateAlbumPictureActivity(IDatabase database, IPublishEndpoint publishEndpoint, ILogger logger) + : UpdatePicturesActivity(database, publishEndpoint, logger) +{ + public const string ExecuteEndpointName = "update-album-picture"; + + protected override Task FindAsync(Guid id, CancellationToken cancellationToken) => + Database.Albums.SingleOrDefaultAsync(album => album.Id == id, cancellationToken); + + protected override PictureNames Read(Album album) => ReadPictures(album.Pictures); + + protected override void Write(Album album, PictureNames names, bool succeeded) => + WritePictures(album.Pictures, names); + + protected override object ProcessedEvent(Guid id) => new AlbumPictureProcessed(id); +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs deleted file mode 100644 index d62995b..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs +++ /dev/null @@ -1,110 +0,0 @@ -using MassTransit; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Application.Events; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Logs; - -namespace Musify.Infrastructure.MassTransit.Activities.Pictures; - -internal class UpdatePlayListPictureActivity( - IDatabase database, - IPublishEndpoint publishEndpoint, - ILogger logger) - : IActivity -{ - public const string ExecuteEndpointName = "update-play-list-picture"; - - public async Task Execute(ExecuteContext executeContext) - { - var smallResizedVariable = executeContext.GetVariable(executeContext.Arguments.SmallPictureVariable); - ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable)); - var mediumResizedVariable = executeContext.GetVariable(executeContext.Arguments.MediumPictureVariable); - ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable)); - var largeResizedVariable = executeContext.GetVariable(executeContext.Arguments.LargePictureVariable); - ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable)); - - try - { - var playList = await database.PlayLists.FindAsync( - [executeContext.Arguments.PlayListId], - cancellationToken: executeContext.CancellationToken); - - if (playList == null) - { - logger.LogWarning("Playlist {PlayListId} not found", - executeContext.Arguments.PlayListId); - throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found"); - } - - if (playList.Pictures == null) - { - logger.LogWarning("Playlist {PlayListId} has no pending picture upload", - executeContext.Arguments.PlayListId); - throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} has no pending picture upload"); - } - - var log = new UpdatePlayListPictureLog( - playList.Id, - playList.Pictures.OriginalName, - playList.Pictures.SmallName, - playList.Pictures.MediumName, - playList.Pictures.LargeName); - - playList.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey); - playList.Pictures.SmallName = Path.GetFileName(smallResizedVariable); - playList.Pictures.MediumName = Path.GetFileName(mediumResizedVariable); - playList.Pictures.LargeName = Path.GetFileName(largeResizedVariable); - - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Updated playlist {PlayListId} pictures", - executeContext.Arguments.PlayListId); - - await publishEndpoint.Publish( - new PlayListPictureProcessed(executeContext.Arguments.PlayListId), - executeContext.CancellationToken); - - return executeContext.Completed(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to update playlist {PlayListId} pictures", - executeContext.Arguments.PlayListId); - throw; - } - } - - public async Task Compensate(CompensateContext compensateContext) - { - try - { - var playList = await database.PlayLists.FindAsync( - [compensateContext.Log.PlayListId], - cancellationToken: compensateContext.CancellationToken); - - if (playList == null) - { - return compensateContext.Compensated(); - } - - playList.Pictures = new EntityPictures - { - OriginalName = compensateContext.Log.PreviousOriginalPictureKey, - SmallName = compensateContext.Log.PreviousSmallPictureKey, - MediumName = compensateContext.Log.PreviousMediumPictureKey, - LargeName = compensateContext.Log.PreviousLargePictureKey - }; - - await database.SaveChangesAsync(compensateContext.CancellationToken); - - return compensateContext.Compensated(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to compensate playlist {PlayListId} pictures", compensateContext.Log.PlayListId); - return compensateContext.Failed(exception); - } - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs deleted file mode 100644 index e209d28..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs +++ /dev/null @@ -1,100 +0,0 @@ -using MassTransit; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Application.Events; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Logs; - -namespace Musify.Infrastructure.MassTransit.Activities.Pictures; - -internal class UpdateTrackPictureActivity( - IDatabase database, - IPublishEndpoint publishEndpoint, - ILogger logger) - : IActivity -{ - public const string ExecuteEndpointName = "update-track-picture"; - - public async Task Execute(ExecuteContext executeContext) - { - var smallResizedVariable = executeContext.GetVariable(executeContext.Arguments.SmallPictureVariable); - ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable)); - var mediumResizedVariable = executeContext.GetVariable(executeContext.Arguments.MediumPictureVariable); - ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable)); - var largeResizedVariable = executeContext.GetVariable(executeContext.Arguments.LargePictureVariable); - ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable)); - - try - { - var track = await database.Tracks.FindAsync( - [executeContext.Arguments.TrackId], - cancellationToken: executeContext.CancellationToken); - - if (track == null) - { - logger.LogWarning("Track {TrackId} not found", - executeContext.Arguments.TrackId); - throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found"); - } - - var log = new UpdateTrackPictureLog( - track.Id, - track.Pictures.OriginalName, - track.Pictures.SmallName, - track.Pictures.MediumName, - track.Pictures.LargeName); - - track.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey); - track.Pictures.SmallName = Path.GetFileName(smallResizedVariable); - track.Pictures.MediumName = Path.GetFileName(mediumResizedVariable); - track.Pictures.LargeName = Path.GetFileName(largeResizedVariable); - track.Pictures.ProcessingStatus = ProcessingStatus.Completed; - - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Updated track {TrackId} pictures", - executeContext.Arguments.TrackId); - - await publishEndpoint.Publish( - new TrackPictureProcessed(executeContext.Arguments.TrackId), - executeContext.CancellationToken); - - return executeContext.Completed(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to update track {TrackId} pictures", - executeContext.Arguments.TrackId); - throw; - } - } - - public async Task Compensate(CompensateContext compensateContext) - { - try - { - var track = await database.Tracks.FindAsync( - [compensateContext.Log.TrackId], - cancellationToken: compensateContext.CancellationToken); - - if (track == null) - return compensateContext.Compensated(); - - track.Pictures.OriginalName = compensateContext.Log.PreviousOriginalPictureKey; - track.Pictures.SmallName = compensateContext.Log.PreviousSmallPictureKey; - track.Pictures.MediumName = compensateContext.Log.PreviousMediumPictureKey; - track.Pictures.LargeName = compensateContext.Log.PreviousLargePictureKey; - track.Pictures.ProcessingStatus = ProcessingStatus.Failed; - - await database.SaveChangesAsync(compensateContext.CancellationToken); - - return compensateContext.Compensated(); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to compensate track {TrackId} pictures", compensateContext.Log.TrackId); - return compensateContext.Failed(exception); - } - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/DeletePlayListFromDbActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/DeletePlayListFromDbActivity.cs deleted file mode 100644 index 7ef578a..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/DeletePlayListFromDbActivity.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.PlayLists; - -internal class DeletePlayListFromDbActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "delete-play-list-from-db"; - - public async Task Execute(ExecuteContext executeContext) - { - var playList = await database.PlayLists - .SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken); - - if (playList == null) - { - logger.LogInformation( - "PlayList {PlayListId} not found while deleting from DB, skipping", - executeContext.Arguments.PlayListId); - return executeContext.Completed(); - } - - database.PlayLists.Remove(playList); - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Deleted playlist {PlayListId} from DB", playList.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsFailedActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsFailedActivity.cs deleted file mode 100644 index fa15beb..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsFailedActivity.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.PlayLists; - -internal class MarkPlayListAsFailedActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-play-list-as-failed"; - - public async Task Execute(ExecuteContext executeContext) - { - var playList = await database.PlayLists - .SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken); - - if (playList != null) - { - playList.LifeCycleStatus = LifeCycleStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - logger.LogInformation("Marked playlist {PlayListId} as failed", executeContext.Arguments.PlayListId); - } - - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsRemovingActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsRemovingActivity.cs deleted file mode 100644 index 52f0afb..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/PlayLists/MarkPlayListAsRemovingActivity.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.PlayLists; - -internal class MarkPlayListAsRemovingActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-play-list-as-removing"; - - public async Task Execute(ExecuteContext executeContext) - { - var playList = await database.PlayLists - .SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken); - - if (playList == null) - { - logger.LogInformation( - "PlayList {PlayListId} not found while marking as removing, skipping", - executeContext.Arguments.PlayListId); - return executeContext.Completed(); - } - - playList.LifeCycleStatus = LifeCycleStatus.Removing; - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Marked playlist {PlayListId} as removing", playList.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/DeleteTrackFromDbActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/DeleteTrackFromDbActivity.cs deleted file mode 100644 index df3592e..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/DeleteTrackFromDbActivity.cs +++ /dev/null @@ -1,35 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Tracks; - -internal class DeleteTrackFromDbActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "delete-track-from-db"; - - public async Task Execute(ExecuteContext executeContext) - { - var track = await database.Tracks - .SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - - if (track == null) - { - logger.LogInformation( - "Track {TrackId} not found while deleting from DB, skipping", - executeContext.Arguments.TrackId); - return executeContext.Completed(); - } - - database.Tracks.Remove(track); - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Deleted track {TrackId} from DB", track.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsFailedActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsFailedActivity.cs deleted file mode 100644 index 4d73742..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsFailedActivity.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Tracks; - -internal class MarkTrackAsFailedActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-track-as-failed"; - - public async Task Execute(ExecuteContext executeContext) - { - var track = await database.Tracks - .SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - - if (track != null) - { - track.LifeCycleStatus = LifeCycleStatus.Failed; - await database.SaveChangesAsync(executeContext.CancellationToken); - logger.LogInformation("Marked track {TrackId} as failed", executeContext.Arguments.TrackId); - } - - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsRemovingActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsRemovingActivity.cs deleted file mode 100644 index 1b22711..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Tracks/MarkTrackAsRemovingActivity.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MassTransit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; - -namespace Musify.Infrastructure.MassTransit.Activities.Tracks; - -internal class MarkTrackAsRemovingActivity( - IDatabase database, - ILogger logger) - : IExecuteActivity -{ - public const string ExecuteEndpointName = "mark-track-as-removing"; - - public async Task Execute(ExecuteContext executeContext) - { - var track = await database.Tracks - .SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken); - - if (track == null) - { - logger.LogInformation( - "Track {TrackId} not found while marking as removing, skipping", - executeContext.Arguments.TrackId); - return executeContext.Completed(); - } - - track.LifeCycleStatus = LifeCycleStatus.Removing; - await database.SaveChangesAsync(executeContext.CancellationToken); - - logger.LogInformation("Marked track {TrackId} as removing", track.Id); - return executeContext.Completed(); - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteAlbumFromDbArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteAlbumFromDbArguments.cs deleted file mode 100644 index ec2a2ca..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteAlbumFromDbArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record DeleteAlbumFromDbArguments( - Guid AlbumId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsFailedArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteEntityArguments.cs similarity index 50% rename from backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsFailedArguments.cs rename to backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteEntityArguments.cs index ae3be70..b84ea8b 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsFailedArguments.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteEntityArguments.cs @@ -1,3 +1,3 @@ namespace Musify.Infrastructure.MassTransit.Arguments; -public record MarkTrackAsFailedArguments(Guid TrackId); +internal record DeleteEntityArguments(Guid Id); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeletePlayListFromDbArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeletePlayListFromDbArguments.cs deleted file mode 100644 index d5b85f3..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeletePlayListFromDbArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record DeletePlayListFromDbArguments( - Guid PlayListId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteTrackFromDbArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteTrackFromDbArguments.cs deleted file mode 100644 index d3c9e6d..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/DeleteTrackFromDbArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record DeleteTrackFromDbArguments( - Guid TrackId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsFailedArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsFailedArguments.cs deleted file mode 100644 index 36dd356..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsFailedArguments.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -public record MarkAlbumAsFailedArguments(Guid AlbumId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsRemovingArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsRemovingArguments.cs deleted file mode 100644 index 65dc6d0..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkAlbumAsRemovingArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record MarkAlbumAsRemovingArguments( - Guid AlbumId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkLifeCycleArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkLifeCycleArguments.cs new file mode 100644 index 0000000..53bd438 --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkLifeCycleArguments.cs @@ -0,0 +1,5 @@ +using Musify.Domain.ValueObjects; + +namespace Musify.Infrastructure.MassTransit.Arguments; + +internal record MarkLifeCycleArguments(Guid Id, LifeCycleStatus Status); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsFailedArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsFailedArguments.cs deleted file mode 100644 index e5e0436..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsFailedArguments.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -public record MarkPlayListAsFailedArguments(Guid PlayListId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsRemovingArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsRemovingArguments.cs deleted file mode 100644 index 5165864..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkPlayListAsRemovingArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record MarkPlayListAsRemovingArguments( - Guid PlayListId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsRemovingArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsRemovingArguments.cs deleted file mode 100644 index 96c25a7..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/MarkTrackAsRemovingArguments.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -internal record MarkTrackAsRemovingArguments( - Guid TrackId); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateAlbumPictureArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateAlbumPictureArguments.cs deleted file mode 100644 index 6e9129c..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateAlbumPictureArguments.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -public record UpdateAlbumPictureArguments( - Guid AlbumId, - string OriginalPictureKey, - string SmallPictureVariable, - string MediumPictureVariable, - string LargePictureVariable); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePicturesArguments.cs similarity index 75% rename from backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs rename to backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePicturesArguments.cs index 37a1ef4..18270ed 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePicturesArguments.cs @@ -1,7 +1,7 @@ namespace Musify.Infrastructure.MassTransit.Arguments; -public record UpdateTrackPictureArguments( - Guid TrackId, +public record UpdatePicturesArguments( + Guid SubjectId, string OriginalPictureKey, string SmallPictureVariable, string MediumPictureVariable, diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs b/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs deleted file mode 100644 index 7efcc0d..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Arguments; - -public record UpdatePlayListPictureArguments( - Guid PlayListId, - string OriginalPictureKey, - string SmallPictureVariable, - string MediumPictureVariable, - string LargePictureVariable); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/AlbumProcessingFailedConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/AlbumProcessingFailedConsumer.cs index d076722..a8b46e2 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/AlbumProcessingFailedConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/AlbumProcessingFailedConsumer.cs @@ -1,37 +1,18 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; -using Musify.Infrastructure.MassTransit.Activities.Albums; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Consumers; public class AlbumProcessingFailedConsumer(IBus bus) : IConsumer { - public async Task Consume(ConsumeContext context) + public Task Consume(ConsumeContext context) { var message = context.Message; - var builder = new RoutingSlipBuilder(NewId.NextGuid()); - builder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey)) - { - builder.AddActivity( - ActivityNames.RemoveAlbumOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey)); - } - - builder.AddActivity( - ActivityNames.MarkAlbumAsFailed, - EndpointHelper.BuildExecuteActivityUri(MarkAlbumAsFailedActivity.ExecuteEndpointName), - new MarkAlbumAsFailedArguments(message.AlbumId)); - - await bus.Execute(builder.Build()); + return ProcessingFailedCleanup.ExecuteAsync( + bus, message.AlbumId, ActivityNames.MarkAlbumAsFailed, MarkAlbumLifeCycleActivity.ExecuteEndpointName, message.Bucket, + (ActivityNames.RemoveAlbumOriginalPicture, message.PictureKey)); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateAlbumConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateAlbumConsumer.cs index 74c2dce..1b6e0c5 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateAlbumConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateAlbumConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class CreateAlbumConsumer( - IBus bus, - AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder) +public class CreateAlbumConsumer(IBus bus, PictureSourceRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreatePlayListConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreatePlayListConsumer.cs index 008dd63..012df87 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreatePlayListConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreatePlayListConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class CreatePlayListConsumer( - IBus bus, - PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder) +public class CreatePlayListConsumer(IBus bus, PictureSourceRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateTrackConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateTrackConsumer.cs index 0bdd8d2..9a7cb5e 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateTrackConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/CreateTrackConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class CreateTrackConsumer( - IBus bus, - CreateTrackRoutingSlipBuilder routingSlipBuilder) +public class CreateTrackConsumer(IBus bus, CreateTrackRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteAlbumConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteAlbumConsumer.cs index cf9b4fc..4802a43 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteAlbumConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteAlbumConsumer.cs @@ -4,21 +4,15 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class DeleteAlbumConsumer( - IBus bus, - DeleteAlbumRoutingSlipBuilder routingSlipBuilder) +public class DeleteAlbumConsumer(IBus bus, DeleteRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) + public async Task Consume(ConsumeContext context) { - var routingSlip = (await routingSlipBuilder - .BuildAsync( - consumeContext.Message.AlbumId, - consumeContext.Message.UserId, - consumeContext.CorrelationId, - consumeContext.CancellationToken)) - .Build(); + var message = context.Message; + var routingSlip = await routingSlipBuilder.BuildAlbumAsync( + message.AlbumId, message.UserId, context.CorrelationId, context.CancellationToken); - await bus.Execute(routingSlip); + await bus.Execute(routingSlip.Build()); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeletePlayListConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeletePlayListConsumer.cs index fea8f01..8175eba 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeletePlayListConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeletePlayListConsumer.cs @@ -4,21 +4,15 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class DeletePlayListConsumer( - IBus bus, - DeletePlayListRoutingSlipBuilder routingSlipBuilder) +public class DeletePlayListConsumer(IBus bus, DeleteRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) + public async Task Consume(ConsumeContext context) { - var routingSlip = (await routingSlipBuilder - .BuildAsync( - consumeContext.Message.PlayListId, - consumeContext.Message.UserId, - consumeContext.CorrelationId, - consumeContext.CancellationToken)) - .Build(); + var message = context.Message; + var routingSlip = await routingSlipBuilder.BuildPlayListAsync( + message.PlayListId, message.UserId, context.CorrelationId, context.CancellationToken); - await bus.Execute(routingSlip); + await bus.Execute(routingSlip.Build()); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteTrackConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteTrackConsumer.cs index 9d3aa28..4d2b123 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteTrackConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/DeleteTrackConsumer.cs @@ -4,17 +4,15 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class DeleteTrackConsumer( - IBus bus, - DeleteTrackRoutingSlipBuilder routingSlipBuilder) +public class DeleteTrackConsumer(IBus bus, DeleteRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) + public async Task Consume(ConsumeContext context) { - var routingSlip = (await routingSlipBuilder - .BuildAsync(consumeContext.Message.TrackId, consumeContext.Message.UserId, consumeContext.CorrelationId, consumeContext.CancellationToken)) - .Build(); + var message = context.Message; + var routingSlip = await routingSlipBuilder.BuildTrackAsync( + message.TrackId, message.UserId, context.CorrelationId, context.CancellationToken); - await bus.Execute(routingSlip); + await bus.Execute(routingSlip.Build()); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/EndpointHelper.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/EndpointHelper.cs deleted file mode 100644 index 945661b..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/EndpointHelper.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Consumers; - -internal static class EndpointHelper -{ - internal static Uri BuildExecuteActivityUri(string endpointName) => new($"queue:{endpointName}_execute"); - - internal static Uri BuildConsumerUri(string endpointName) => new($"queue:{endpointName}"); -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/PlayListProcessingFailedConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/PlayListProcessingFailedConsumer.cs index c06ccf4..78bcb62 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/PlayListProcessingFailedConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/PlayListProcessingFailedConsumer.cs @@ -1,37 +1,18 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.PlayLists; -using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Consumers; public class PlayListProcessingFailedConsumer(IBus bus) : IConsumer { - public async Task Consume(ConsumeContext context) + public Task Consume(ConsumeContext context) { var message = context.Message; - var builder = new RoutingSlipBuilder(NewId.NextGuid()); - builder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey)) - { - builder.AddActivity( - ActivityNames.RemovePlayListOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey)); - } - - builder.AddActivity( - ActivityNames.MarkPlayListAsFailed, - EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsFailedActivity.ExecuteEndpointName), - new MarkPlayListAsFailedArguments(message.PlayListId)); - - await bus.Execute(builder.Build()); + return ProcessingFailedCleanup.ExecuteAsync( + bus, message.PlayListId, ActivityNames.MarkPlayListAsFailed, MarkPlayListLifeCycleActivity.ExecuteEndpointName, message.Bucket, + (ActivityNames.RemovePlayListOriginalPicture, message.PictureKey)); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingFailedCleanup.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingFailedCleanup.cs new file mode 100644 index 0000000..434ad9b --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingFailedCleanup.cs @@ -0,0 +1,34 @@ +using MassTransit; +using Musify.Domain.ValueObjects; +using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.RoutingSlip; + +namespace Musify.Infrastructure.MassTransit.Consumers; + +internal static class ProcessingFailedCleanup +{ + /// Removes whatever files were left behind and marks the entity as failed. + public static Task ExecuteAsync( + IBus bus, + Guid subjectId, + string markStepName, + string markEndpointName, + string? bucket, + params (string StepName, string? Key)[] files) + { + var builder = RoutingSlips.Create(correlationId: null); + + if (!string.IsNullOrWhiteSpace(bucket)) + { + foreach (var (stepName, key) in files) + { + if (!string.IsNullOrWhiteSpace(key)) + builder.AddRemoveFile(stepName, bucket, key); + } + } + + builder.AddStep(markStepName, markEndpointName, new MarkLifeCycleArguments(subjectId, LifeCycleStatus.Failed)); + + return bus.Execute(builder.Build()); + } +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingSlipFaultConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingSlipFaultConsumer.cs index 8c1dca6..c41dd54 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingSlipFaultConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/ProcessingSlipFaultConsumer.cs @@ -1,6 +1,7 @@ using MassTransit; using MassTransit.Courier.Contracts; using Musify.Application.Events; +using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Consumers; diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs index d04ca6a..870e010 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs @@ -1,6 +1,7 @@ using MassTransit; using MassTransit.Courier.Contracts; using Microsoft.Extensions.Logging; +using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Consumers; @@ -32,14 +33,9 @@ public class RoutingSlipCleanUpConsumer( Directory.Delete(pathString, recursive: true); logger.LogInformation("Deleted temporal directory at path {Path}", pathString); } - catch (DirectoryNotFoundException directoryNotFoundException) + catch (DirectoryNotFoundException) { - logger.LogWarning(directoryNotFoundException, "Temporal directory at path {Path} was not found for deletion", pathString); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to delete temporal directory at path {Path}", pathString); - throw; + logger.LogWarning("Temporal directory at path {Path} was not found for deletion", pathString); } return Task.CompletedTask; diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/TrackProcessingFailedConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/TrackProcessingFailedConsumer.cs index 6a0e645..c2f5a3b 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/TrackProcessingFailedConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/TrackProcessingFailedConsumer.cs @@ -1,45 +1,19 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.Tracks; -using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; using Musify.Infrastructure.MassTransit.RoutingSlip; namespace Musify.Infrastructure.MassTransit.Consumers; public class TrackProcessingFailedConsumer(IBus bus) : IConsumer { - public async Task Consume(ConsumeContext context) + public Task Consume(ConsumeContext context) { var message = context.Message; - var builder = new RoutingSlipBuilder(NewId.NextGuid()); - builder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey)) - { - builder.AddActivity( - ActivityNames.RemoveTrackOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey)); - } - - if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.AudioKey)) - { - builder.AddActivity( - ActivityNames.RemoveTrackOriginalAudio, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments(message.Bucket, message.AudioKey)); - } - - builder.AddActivity( - ActivityNames.MarkTrackAsFailed, - EndpointHelper.BuildExecuteActivityUri(MarkTrackAsFailedActivity.ExecuteEndpointName), - new MarkTrackAsFailedArguments(message.TrackId)); - - await bus.Execute(builder.Build()); + return ProcessingFailedCleanup.ExecuteAsync( + bus, message.TrackId, ActivityNames.MarkTrackAsFailed, MarkTrackLifeCycleActivity.ExecuteEndpointName, message.Bucket, + (ActivityNames.RemoveTrackOriginalPicture, message.PictureKey), + (ActivityNames.RemoveTrackOriginalAudio, message.AudioKey)); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureConsumer.cs index 9e28c7d..ad9f8f3 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdateAlbumPictureConsumer( - IBus bus, - PictureWorkflowRoutingSlipBuilder routingSlipBuilder) +public class UpdateAlbumPictureConsumer(IBus bus, PictureWorkflowRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureSourceConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureSourceConsumer.cs index 928dd5a..4069246 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureSourceConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateAlbumPictureSourceConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdateAlbumPictureSourceConsumer( - IBus bus, - AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder) +public class UpdateAlbumPictureSourceConsumer(IBus bus, PictureSourceRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs index ba52e76..d89df5e 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdatePlayListPictureConsumer( - IBus bus, - PictureWorkflowRoutingSlipBuilder routingSlipBuilder) +public class UpdatePlayListPictureConsumer(IBus bus, PictureWorkflowRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureSourceConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureSourceConsumer.cs index ea0605b..8082d79 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureSourceConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdatePlayListPictureSourceConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdatePlayListPictureSourceConsumer( - IBus bus, - PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder) +public class UpdatePlayListPictureSourceConsumer(IBus bus, PictureSourceRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackAudioConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackAudioConsumer.cs index 828aba8..221dc24 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackAudioConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackAudioConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdateTrackAudioConsumer( - IBus bus, - AudioWorkflowRoutingSlipBuilder routingSlipBuilder) +public class UpdateTrackAudioConsumer(IBus bus, AudioWorkflowRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs index 9f4b1e6..d26de5a 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs @@ -4,17 +4,9 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Consumers; -public class UpdateTrackPictureConsumer( - IBus bus, - PictureWorkflowRoutingSlipBuilder routingSlipBuilder) +public class UpdateTrackPictureConsumer(IBus bus, PictureWorkflowRoutingSlipBuilder routingSlipBuilder) : IConsumer { - public async Task Consume(ConsumeContext consumeContext) - { - var routingSlip = routingSlipBuilder - .Build(consumeContext.Message, consumeContext.CorrelationId) - .Build(); - - await bus.Execute(routingSlip); - } + public Task Consume(ConsumeContext context) => + bus.Execute(routingSlipBuilder.Build(context.Message, context.CorrelationId).Build()); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs index f86a6cc..b039d5a 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs @@ -11,6 +11,7 @@ using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Consumers; using Musify.Infrastructure.MassTransit.Logs; using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; namespace Musify.Infrastructure.MassTransit; @@ -19,61 +20,55 @@ public static partial class MassTransitDependencyInjection private static void RegisterRoutingSlipBuilders(IServiceCollection services) { services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } private static void RegisterConsumersAndActivities(IBusRegistrationConfigurator options) { - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); - options.AddConsumer(); options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); + options.AddConsumer(); - options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); options.AddExecuteActivity(); options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); - options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); + options.AddExecuteActivity(); - options.AddActivity(); - options.AddActivity(); - options.AddActivity(); - options.AddActivity(); + options.AddActivity(); + options.AddActivity(); options.AddActivity(); + options.AddActivity(); options.AddActivity(); options.AddActivity(); + options.AddActivity(); + options.AddActivity(); options.AddActivity(); + options.AddActivity(); options.AddActivity(); - options.AddActivity(); - options.AddActivity(); } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs index e125e39..8ed3d83 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs @@ -18,14 +18,7 @@ public static partial class MassTransitDependencyInjection services.AddScoped(); services.AddMassTransit(options => { - options.AddEntityFrameworkOutbox(outbox => - { - outbox.UsePostgres(); - outbox.UseBusOutbox(); - - outbox.QueryDelay = TimeSpan.FromSeconds(1); - outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30); - }); + AddOutbox(options); options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) => ConfigureRabbitMqHost( @@ -46,42 +39,20 @@ public static partial class MassTransitDependencyInjection services.AddMassTransit(options => { - options.AddEntityFrameworkOutbox(outbox => - { - outbox.UsePostgres(); - outbox.UseBusOutbox(); - - outbox.QueryDelay = TimeSpan.FromSeconds(1); - outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30); - }); + AddOutbox(options); options.SetKebabCaseEndpointNameFormatter(); RegisterConsumersAndActivities(options); options.AddSagaStateMachine() - .EntityFrameworkRepository(repository => - { - repository.ConcurrencyMode = ConcurrencyMode.Pessimistic; - repository.ExistingDbContext(); - repository.UsePostgres(); - }); + .EntityFrameworkRepository(ConfigureSagaRepository); options.AddSagaStateMachine() - .EntityFrameworkRepository(repository => - { - repository.ConcurrencyMode = ConcurrencyMode.Pessimistic; - repository.ExistingDbContext(); - repository.UsePostgres(); - }); + .EntityFrameworkRepository(ConfigureSagaRepository); options.AddSagaStateMachine() - .EntityFrameworkRepository(repository => - { - repository.ConcurrencyMode = ConcurrencyMode.Pessimistic; - repository.ExistingDbContext(); - repository.UsePostgres(); - }); + .EntityFrameworkRepository(ConfigureSagaRepository); options.AddConfigureEndpointsCallback((registrationContext, _, endpointConfigurator) => { @@ -102,6 +73,24 @@ public static partial class MassTransitDependencyInjection return services; } + private static void AddOutbox(IBusRegistrationConfigurator options) => + options.AddEntityFrameworkOutbox(outbox => + { + outbox.UsePostgres(); + outbox.UseBusOutbox(); + + outbox.QueryDelay = TimeSpan.FromSeconds(1); + outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30); + }); + + private static void ConfigureSagaRepository(IEntityFrameworkSagaRepositoryConfigurator repository) + where TSaga : class, ISaga + { + repository.ConcurrencyMode = ConcurrencyMode.Pessimistic; + repository.ExistingDbContext(); + repository.UsePostgres(); + } + private static void UseStandardRetry(IReceiveEndpointConfigurator endpointConfigurator) => endpointConfigurator.UseMessageRetry(retryConfigurator => retryConfigurator.Exponential( diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateAlbumPictureLog.cs b/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateAlbumPictureLog.cs deleted file mode 100644 index d14f075..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateAlbumPictureLog.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Logs; - -public record UpdateAlbumPictureLog( - Guid AlbumId, - string PreviousOriginalPictureKey, - string? PreviousSmallPictureKey, - string? PreviousMediumPictureKey, - string? PreviousLargePictureKey); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePicturesLog.cs b/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePicturesLog.cs new file mode 100644 index 0000000..d17297d --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePicturesLog.cs @@ -0,0 +1,8 @@ +namespace Musify.Infrastructure.MassTransit.Logs; + +public record UpdatePicturesLog( + Guid SubjectId, + string? PreviousOriginalName, + string? PreviousSmallName, + string? PreviousMediumName, + string? PreviousLargeName); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs b/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs deleted file mode 100644 index 8ee81b5..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Logs; - -public record UpdatePlayListPictureLog( - Guid PlayListId, - string PreviousOriginalPictureKey, - string? PreviousSmallPictureKey, - string? PreviousMediumPictureKey, - string? PreviousLargePictureKey); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs b/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs deleted file mode 100644 index 316b827..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Musify.Infrastructure.MassTransit.Logs; - -public record UpdateTrackPictureLog( - Guid TrackId, - string? PreviousOriginalPictureKey, - string? PreviousSmallPictureKey, - string? PreviousMediumPictureKey, - string? PreviousLargePictureKey); diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AlbumPictureSourceRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AlbumPictureSourceRoutingSlipBuilder.cs deleted file mode 100644 index 5beae29..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AlbumPictureSourceRoutingSlipBuilder.cs +++ /dev/null @@ -1,87 +0,0 @@ -using MassTransit; -using MassTransit.Courier.Contracts; -using Musify.Application.Events; -using Musify.Application.Shared; -using Musify.Infrastructure.MassTransit.Activities.Albums; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.UploadIntents; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; - -namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; - -public class AlbumPictureSourceRoutingSlipBuilder -{ - public RoutingSlipBuilder Build(CreateAlbumResourcesEvent message, Guid? correlationId) - => BuildSlip( - message.AlbumId, - message.PictureIntentId, - message.Bucket, - message.PictureSourceKey, - message.PictureDestinationKey, - message.Sizes, - correlationId, - RoutingSlipVariableNames.ProcessKinds.AlbumCreation); - - public RoutingSlipBuilder Build(UpdateAlbumPictureSourceEvent message, Guid? correlationId) - => BuildSlip( - message.AlbumId, - message.PictureIntentId, - message.Bucket, - message.PictureSourceKey, - message.PictureDestinationKey, - message.Sizes, - correlationId, - RoutingSlipVariableNames.ProcessKinds.AlbumPicture); - - private static RoutingSlipBuilder BuildSlip( - Guid albumId, - Guid pictureIntentId, - string bucket, - string pictureSourceKey, - string pictureDestinationKey, - ImageSizes sizes, - Guid? correlationId, - string processKind) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - routingSlipBuilder.AddActivity( - ActivityNames.CopyPictureToFinal, - EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName), - new CopyFileInBucketArguments( - bucket, - pictureSourceKey, - bucket, - pictureDestinationKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.ConsumeUploadIntents, - EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName), - new ConsumeUploadIntentsArguments([pictureIntentId])); - - routingSlipBuilder.AddActivity( - ActivityNames.PublishAlbumPictureProcessingEvent, - EndpointHelper.BuildExecuteActivityUri(PublishAlbumPictureProcessingEventActivity.ExecuteEndpointName), - new PublishAlbumPictureProcessingEventArguments( - albumId, - bucket, - pictureDestinationKey, - sizes)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, albumId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, processKind); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.Bucket, bucket); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.PictureKey, pictureDestinationKey); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs index c8745b8..b3f1e07 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs @@ -1,71 +1,44 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; using Musify.Infrastructure.Configuration; using Musify.Infrastructure.MassTransit.Activities.Audio; using Musify.Infrastructure.MassTransit.Activities.Files; using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; public class AudioWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration) { - public RoutingSlipBuilder Build(UpdateTrackAudioEvent message, Guid? correlationId) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - routingSlipBuilder.AddActivity( - ActivityNames.GenerateAudioWorkflowPaths, - EndpointHelper.BuildExecuteActivityUri(GenerateAudioWorkflowPathsActivity.ExecuteEndpointName), - new GenerateAudioWorkflowPathsArguments( - message.TrackId, - workerConfiguration.Routes.TemporaryFilesDirectory, - message.SourceKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.DownloadFile, - EndpointHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName), - new DownloadFileFromBucketArguments( - message.SourceBucket, - message.SourceKey, - RoutingSlipVariableNames.Audio.SourceFilePath)); - - routingSlipBuilder.AddActivity( - ActivityNames.TranscodeAudio, - EndpointHelper.BuildExecuteActivityUri(TranscodeAudioActivity.ExecuteEndpointName), - new TranscodeAudioArguments( - message.TrackId, - RoutingSlipVariableNames.Audio.SourceFilePath, - RoutingSlipVariableNames.Workflow.TemporalDirectory)); - - routingSlipBuilder.AddActivity( - ActivityNames.TransferFiles, - EndpointHelper.BuildExecuteActivityUri(TransferFilesToBucketActivity.ExecuteEndpointName), - new TransferFilesToBucketArguments( - message.DestinationBucket, - RoutingSlipVariableNames.Audio.TranscodedDirectory, - message.DestinationFolderKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.UpdateTrackAudio, - EndpointHelper.BuildExecuteActivityUri(UpdateTrackAudioActivity.ExecuteEndpointName), - new UpdateTrackAudioArguments( - message.TrackId, - message.DestinationFolderKey, - RoutingSlipVariableNames.Audio.DurationSeconds)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.TrackId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.TrackAudio); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } + public RoutingSlipBuilder Build(UpdateTrackAudioEvent message, Guid? correlationId) => + RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.GenerateAudioWorkflowPaths, + GenerateAudioWorkflowPathsActivity.ExecuteEndpointName, + new GenerateAudioWorkflowPathsArguments( + message.TrackId, workerConfiguration.Routes.TemporaryFilesDirectory, message.SourceKey)) + .AddStep( + ActivityNames.DownloadFile, + DownloadFileFromBucketActivity.ExecuteEndpointName, + new DownloadFileFromBucketArguments( + message.SourceBucket, message.SourceKey, RoutingSlipVariableNames.Audio.SourceFilePath)) + .AddStep( + ActivityNames.TranscodeAudio, + TranscodeAudioActivity.ExecuteEndpointName, + new TranscodeAudioArguments( + message.TrackId, + RoutingSlipVariableNames.Audio.SourceFilePath, + RoutingSlipVariableNames.Workflow.TemporalDirectory)) + .AddStep( + ActivityNames.TransferFiles, + TransferFilesToBucketActivity.ExecuteEndpointName, + new TransferFilesToBucketArguments( + message.DestinationBucket, + RoutingSlipVariableNames.Audio.TranscodedDirectory, + message.DestinationFolderKey)) + .AddStep( + ActivityNames.UpdateTrackAudio, + UpdateTrackAudioActivity.ExecuteEndpointName, + new UpdateTrackAudioArguments( + message.TrackId, message.DestinationFolderKey, RoutingSlipVariableNames.Audio.DurationSeconds)) + .TrackFaults(message.TrackId, RoutingSlipVariableNames.ProcessKinds.TrackAudio); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/CreateTrackRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/CreateTrackRoutingSlipBuilder.cs index f50a844..d8b1f50 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/CreateTrackRoutingSlipBuilder.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/CreateTrackRoutingSlipBuilder.cs @@ -1,11 +1,9 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; using Musify.Infrastructure.MassTransit.Activities.Files; using Musify.Infrastructure.MassTransit.Activities.Tracks; using Musify.Infrastructure.MassTransit.Activities.UploadIntents; using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; @@ -13,57 +11,35 @@ public class CreateTrackRoutingSlipBuilder { public RoutingSlipBuilder Build(CreateTrackResourcesEvent message, Guid? correlationId) { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.CopyPictureToFinal, + CopyFileInBucketActivity.ExecuteEndpointName, + new CopyFileInBucketArguments(message.Bucket, message.PictureSourceKey, message.Bucket, message.PictureDestinationKey)) + .AddStep( + ActivityNames.CopyAudioToFinal, + CopyFileInBucketActivity.ExecuteEndpointName, + new CopyFileInBucketArguments(message.Bucket, message.AudioSourceKey, message.Bucket, message.AudioDestinationKey)) + .AddStep( + ActivityNames.ConsumeUploadIntents, + ConsumeUploadIntentsActivity.ExecuteEndpointName, + new ConsumeUploadIntentsArguments([message.PictureIntentId, message.AudioIntentId])) + .AddStep( + ActivityNames.PublishTrackProcessingEvents, + PublishTrackProcessingEventsActivity.ExecuteEndpointName, + new PublishTrackProcessingEventsArguments( + message.TrackId, + message.Bucket, + message.PictureDestinationKey, + message.AudioDestinationKey, + message.AudioProcessedFolderKey, + message.Sizes)) + .TrackFaults(message.TrackId, RoutingSlipVariableNames.ProcessKinds.TrackCreation); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); + builder.AddVariable(RoutingSlipVariableNames.Workflow.Bucket, message.Bucket); + builder.AddVariable(RoutingSlipVariableNames.Workflow.PictureKey, message.PictureDestinationKey); + builder.AddVariable(RoutingSlipVariableNames.Workflow.AudioKey, message.AudioDestinationKey); - routingSlipBuilder.AddActivity( - ActivityNames.CopyPictureToFinal, - EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName), - new CopyFileInBucketArguments( - message.Bucket, - message.PictureSourceKey, - message.Bucket, - message.PictureDestinationKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.CopyAudioToFinal, - EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName), - new CopyFileInBucketArguments( - message.Bucket, - message.AudioSourceKey, - message.Bucket, - message.AudioDestinationKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.ConsumeUploadIntents, - EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName), - new ConsumeUploadIntentsArguments( - [message.PictureIntentId, message.AudioIntentId])); - - routingSlipBuilder.AddActivity( - ActivityNames.PublishTrackProcessingEvents, - EndpointHelper.BuildExecuteActivityUri(PublishTrackProcessingEventsActivity.ExecuteEndpointName), - new PublishTrackProcessingEventsArguments( - message.TrackId, - message.Bucket, - message.PictureDestinationKey, - message.AudioDestinationKey, - message.AudioProcessedFolderKey, - message.Sizes)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.TrackId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.TrackCreation); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.Bucket, message.Bucket); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.PictureKey, message.PictureDestinationKey); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.AudioKey, message.AudioDestinationKey); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; + return builder; } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteAlbumRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteAlbumRoutingSlipBuilder.cs deleted file mode 100644 index 95011c6..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteAlbumRoutingSlipBuilder.cs +++ /dev/null @@ -1,93 +0,0 @@ -using MassTransit; -using MassTransit.Courier.Contracts; -using Microsoft.EntityFrameworkCore; -using Musify.Application.Configuration; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.Albums; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; - -namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; - -public class DeleteAlbumRoutingSlipBuilder( - IDatabase database, - AlbumConfiguration albumConfiguration, - ApplicationStorageConfiguration storageConfiguration) -{ - public async Task BuildAsync( - Guid albumId, - long userId, - Guid? correlationId, - CancellationToken cancellationToken) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - var album = await database.Albums - .AsNoTracking() - .SingleOrDefaultAsync(p => p.Id == albumId, cancellationToken); - - routingSlipBuilder.AddActivity( - ActivityNames.MarkAlbumAsRemoving, - EndpointHelper.BuildExecuteActivityUri(MarkAlbumAsRemovingActivity.ExecuteEndpointName), - new MarkAlbumAsRemovingArguments(albumId)); - - if (album?.Pictures != null) - { - var bucket = storageConfiguration.Bucket; - var routes = albumConfiguration.Routes; - - if (album.Pictures.OriginalName is { } originalName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveAlbumOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildOriginalPicturePath(userId, originalName))); - } - - if (album.Pictures.SmallName is { } smallName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveAlbumSmallPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Small, smallName))); - } - - if (album.Pictures.MediumName is { } mediumName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveAlbumMediumPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Medium, mediumName))); - } - - if (album.Pictures.LargeName is { } largeName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveAlbumLargePicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Large, largeName))); - } - } - - routingSlipBuilder.AddActivity( - ActivityNames.DeleteAlbumFromDb, - EndpointHelper.BuildExecuteActivityUri(DeleteAlbumFromDbActivity.ExecuteEndpointName), - new DeleteAlbumFromDbArguments(albumId)); - - return routingSlipBuilder; - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeletePlayListRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeletePlayListRoutingSlipBuilder.cs deleted file mode 100644 index c05a4f0..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeletePlayListRoutingSlipBuilder.cs +++ /dev/null @@ -1,93 +0,0 @@ -using MassTransit; -using MassTransit.Courier.Contracts; -using Microsoft.EntityFrameworkCore; -using Musify.Application.Configuration; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.PlayLists; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; - -namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; - -public class DeletePlayListRoutingSlipBuilder( - IDatabase database, - PlayListConfiguration playListConfiguration, - ApplicationStorageConfiguration storageConfiguration) -{ - public async Task BuildAsync( - Guid playListId, - long userId, - Guid? correlationId, - CancellationToken cancellationToken) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - var playList = await database.PlayLists - .AsNoTracking() - .SingleOrDefaultAsync(p => p.Id == playListId, cancellationToken); - - routingSlipBuilder.AddActivity( - ActivityNames.MarkPlayListAsRemoving, - EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsRemovingActivity.ExecuteEndpointName), - new MarkPlayListAsRemovingArguments(playListId)); - - if (playList?.Pictures != null) - { - var bucket = storageConfiguration.Bucket; - var routes = playListConfiguration.Routes; - - if (playList.Pictures.OriginalName is { } originalName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemovePlayListOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildOriginalPicturePath(userId, originalName))); - } - - if (playList.Pictures.SmallName is { } smallName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemovePlayListSmallPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Small, smallName))); - } - - if (playList.Pictures.MediumName is { } mediumName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemovePlayListMediumPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Medium, mediumName))); - } - - if (playList.Pictures.LargeName is { } largeName) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemovePlayListLargePicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - routes.BuildPicturePath(PictureSize.Large, largeName))); - } - } - - routingSlipBuilder.AddActivity( - ActivityNames.DeletePlayListFromDb, - EndpointHelper.BuildExecuteActivityUri(DeletePlayListFromDbActivity.ExecuteEndpointName), - new DeletePlayListFromDbArguments(playListId)); - - return routingSlipBuilder; - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs new file mode 100644 index 0000000..b13d252 --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs @@ -0,0 +1,135 @@ +using MassTransit; +using Microsoft.EntityFrameworkCore; +using Musify.Application.Configuration; +using Musify.Application.Contracts; +using Musify.Domain.ValueObjects; +using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; + +namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; + +/// Marks an entity as removing, deletes its files from the bucket and finally deletes its row. +public class DeleteRoutingSlipBuilder( + IDatabase database, + TrackConfiguration trackConfiguration, + AlbumConfiguration albumConfiguration, + PlayListConfiguration playListConfiguration, + ApplicationStorageConfiguration storageConfiguration) +{ + private sealed record PictureStepNames(string Original, string Small, string Medium, string Large); + + private static readonly PictureStepNames AlbumSteps = new( + ActivityNames.RemoveAlbumOriginalPicture, ActivityNames.RemoveAlbumSmallPicture, + ActivityNames.RemoveAlbumMediumPicture, ActivityNames.RemoveAlbumLargePicture); + + private static readonly PictureStepNames PlayListSteps = new( + ActivityNames.RemovePlayListOriginalPicture, ActivityNames.RemovePlayListSmallPicture, + ActivityNames.RemovePlayListMediumPicture, ActivityNames.RemovePlayListLargePicture); + + public async Task BuildTrackAsync( + Guid trackId, long userId, Guid? correlationId, CancellationToken cancellationToken) + { + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.MarkTrackAsRemoving, + MarkTrackLifeCycleActivity.ExecuteEndpointName, + new MarkLifeCycleArguments(trackId, LifeCycleStatus.Removing)); + + var track = await database.Tracks.AsNoTracking().SingleOrDefaultAsync(t => t.Id == trackId, cancellationToken); + if (track != null) + { + var bucket = storageConfiguration.Bucket; + var routes = trackConfiguration.Routes; + + if (!string.IsNullOrEmpty(track.Pictures.OriginalName)) + { + builder.AddRemoveFile( + ActivityNames.RemoveTrackOriginalPicture, bucket, + routes.BuildOriginalPicturePath(userId, track.Pictures.OriginalName)); + } + + // Unprocessed tracks still point at the shared preset pictures, which must never be deleted. + if (track.Pictures.IsProcessed) + { + builder.AddRemoveFile(ActivityNames.RemoveTrackSmallPicture, bucket, routes.BuildPicturePath(PictureSize.Small, track.Pictures.SmallName)); + builder.AddRemoveFile(ActivityNames.RemoveTrackMediumPicture, bucket, routes.BuildPicturePath(PictureSize.Medium, track.Pictures.MediumName)); + builder.AddRemoveFile(ActivityNames.RemoveTrackLargePicture, bucket, routes.BuildPicturePath(PictureSize.Large, track.Pictures.LargeName)); + } + + if (!string.IsNullOrEmpty(track.Audio.OriginalName)) + { + builder.AddRemoveFile( + ActivityNames.RemoveTrackOriginalAudio, bucket, + routes.BuildOriginalAudioPath(userId, track.Audio.OriginalName)); + } + + if (track.Audio.IsProcessed) + { + builder.AddRemoveFile( + ActivityNames.RemoveTrackProcessedAudio, bucket, + routes.BuildProcessedAudioPath(track.Audio.FolderName)); + } + } + + return builder.AddStep( + ActivityNames.DeleteTrackFromDb, DeleteTrackActivity.ExecuteEndpointName, new DeleteEntityArguments(trackId)); + } + + public async Task BuildAlbumAsync( + Guid albumId, long userId, Guid? correlationId, CancellationToken cancellationToken) + { + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.MarkAlbumAsRemoving, + MarkAlbumLifeCycleActivity.ExecuteEndpointName, + new MarkLifeCycleArguments(albumId, LifeCycleStatus.Removing)); + + var pictures = await database.Albums.AsNoTracking() + .Where(a => a.Id == albumId) + .Select(a => a.Pictures) + .SingleOrDefaultAsync(cancellationToken); + + AddPictureRemovals(builder, AlbumSteps, albumConfiguration.Routes, userId, pictures); + + return builder.AddStep( + ActivityNames.DeleteAlbumFromDb, DeleteAlbumActivity.ExecuteEndpointName, new DeleteEntityArguments(albumId)); + } + + public async Task BuildPlayListAsync( + Guid playListId, long userId, Guid? correlationId, CancellationToken cancellationToken) + { + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.MarkPlayListAsRemoving, + MarkPlayListLifeCycleActivity.ExecuteEndpointName, + new MarkLifeCycleArguments(playListId, LifeCycleStatus.Removing)); + + var pictures = await database.PlayLists.AsNoTracking() + .Where(p => p.Id == playListId) + .Select(p => p.Pictures) + .SingleOrDefaultAsync(cancellationToken); + + AddPictureRemovals(builder, PlayListSteps, playListConfiguration.Routes, userId, pictures); + + return builder.AddStep( + ActivityNames.DeletePlayListFromDb, DeletePlayListActivity.ExecuteEndpointName, new DeleteEntityArguments(playListId)); + } + + private void AddPictureRemovals( + RoutingSlipBuilder builder, PictureStepNames steps, PictureRoutes routes, long userId, EntityPictures? pictures) + { + if (pictures == null) + return; + + var bucket = storageConfiguration.Bucket; + + if (pictures.OriginalName is { } original) + builder.AddRemoveFile(steps.Original, bucket, routes.BuildOriginalPicturePath(userId, original)); + if (pictures.SmallName is { } small) + builder.AddRemoveFile(steps.Small, bucket, routes.BuildPicturePath(PictureSize.Small, small)); + if (pictures.MediumName is { } medium) + builder.AddRemoveFile(steps.Medium, bucket, routes.BuildPicturePath(PictureSize.Medium, medium)); + if (pictures.LargeName is { } large) + builder.AddRemoveFile(steps.Large, bucket, routes.BuildPicturePath(PictureSize.Large, large)); + } +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteTrackRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteTrackRoutingSlipBuilder.cs deleted file mode 100644 index bdd7062..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteTrackRoutingSlipBuilder.cs +++ /dev/null @@ -1,106 +0,0 @@ -using MassTransit; -using MassTransit.Courier.Contracts; -using Microsoft.EntityFrameworkCore; -using Musify.Application.Configuration; -using Musify.Application.Contracts; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.Tracks; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; - -namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; - -public class DeleteTrackRoutingSlipBuilder( - IDatabase database, - TrackConfiguration trackConfiguration, - ApplicationStorageConfiguration storageConfiguration) -{ - public async Task BuildAsync( - Guid trackId, - long userId, - Guid? correlationId, - CancellationToken cancellationToken) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - var track = await database.Tracks - .AsNoTracking() - .SingleOrDefaultAsync(t => t.Id == trackId, cancellationToken); - - routingSlipBuilder.AddActivity( - ActivityNames.MarkTrackAsRemoving, - EndpointHelper.BuildExecuteActivityUri(MarkTrackAsRemovingActivity.ExecuteEndpointName), - new MarkTrackAsRemovingArguments(trackId)); - - if (track != null) - { - var bucket = storageConfiguration.Bucket; - - if (!string.IsNullOrEmpty(track.Pictures.OriginalName)) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackOriginalPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildOriginalPicturePath(userId, track.Pictures.OriginalName))); - } - - if (track.Pictures.IsProcessed) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackSmallPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildPicturePath(PictureSize.Small, track.Pictures.SmallName))); - - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackMediumPicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildPicturePath(PictureSize.Medium, track.Pictures.MediumName))); - - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackLargePicture, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildPicturePath(PictureSize.Large, track.Pictures.LargeName))); - } - - if (!string.IsNullOrEmpty(track.Audio.OriginalName)) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackOriginalAudio, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildOriginalAudioPath(userId, track.Audio.OriginalName))); - } - - if (track.Audio.IsProcessed) - { - routingSlipBuilder.AddActivity( - ActivityNames.RemoveTrackProcessedAudio, - EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName), - new RemoveFileFromBucketArguments( - bucket, - trackConfiguration.Routes.BuildProcessedAudioPath(track.Audio.FolderName))); - } - } - - routingSlipBuilder.AddActivity( - ActivityNames.DeleteTrackFromDb, - EndpointHelper.BuildExecuteActivityUri(DeleteTrackFromDbActivity.ExecuteEndpointName), - new DeleteTrackFromDbArguments(trackId)); - - return routingSlipBuilder; - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureSourceRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureSourceRoutingSlipBuilder.cs new file mode 100644 index 0000000..2069f14 --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureSourceRoutingSlipBuilder.cs @@ -0,0 +1,74 @@ +using MassTransit; +using Musify.Application.Events; +using Musify.Infrastructure.MassTransit.Activities.Albums; +using Musify.Infrastructure.MassTransit.Activities.Files; +using Musify.Infrastructure.MassTransit.Activities.PlayLists; +using Musify.Infrastructure.MassTransit.Activities.UploadIntents; +using Musify.Infrastructure.MassTransit.Arguments; + +namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; + +/// +/// Moves an uploaded album/playlist picture to its final key, consumes the upload intent and hands over to the picture workflow. +/// Creation faults mark the whole entity as failed; update faults only report the picture as failed. +/// +public class PictureSourceRoutingSlipBuilder +{ + public RoutingSlipBuilder Build(CreateAlbumResourcesEvent message, Guid? correlationId) => + BuildSlip( + message.AlbumId, message.PictureIntentId, message.Bucket, message.PictureSourceKey, message.PictureDestinationKey, + correlationId, RoutingSlipVariableNames.ProcessKinds.AlbumCreation, + ActivityNames.PublishAlbumPictureProcessingEvent, PublishAlbumPictureProcessingEventActivity.ExecuteEndpointName, + new PublishAlbumPictureProcessingEventArguments(message.AlbumId, message.Bucket, message.PictureDestinationKey, message.Sizes)); + + public RoutingSlipBuilder Build(UpdateAlbumPictureSourceEvent message, Guid? correlationId) => + BuildSlip( + message.AlbumId, message.PictureIntentId, message.Bucket, message.PictureSourceKey, message.PictureDestinationKey, + correlationId, RoutingSlipVariableNames.ProcessKinds.AlbumPicture, + ActivityNames.PublishAlbumPictureProcessingEvent, PublishAlbumPictureProcessingEventActivity.ExecuteEndpointName, + new PublishAlbumPictureProcessingEventArguments(message.AlbumId, message.Bucket, message.PictureDestinationKey, message.Sizes)); + + public RoutingSlipBuilder Build(CreatePlayListResourcesEvent message, Guid? correlationId) => + BuildSlip( + message.PlayListId, message.PictureIntentId, message.Bucket, message.PictureSourceKey, message.PictureDestinationKey, + correlationId, RoutingSlipVariableNames.ProcessKinds.PlayListCreation, + ActivityNames.PublishPlayListPictureProcessingEvent, PublishPlayListPictureProcessingEventActivity.ExecuteEndpointName, + new PublishPlayListPictureProcessingEventArguments(message.PlayListId, message.Bucket, message.PictureDestinationKey, message.Sizes)); + + public RoutingSlipBuilder Build(UpdatePlayListPictureSourceEvent message, Guid? correlationId) => + BuildSlip( + message.PlayListId, message.PictureIntentId, message.Bucket, message.PictureSourceKey, message.PictureDestinationKey, + correlationId, RoutingSlipVariableNames.ProcessKinds.PlayListPicture, + ActivityNames.PublishPlayListPictureProcessingEvent, PublishPlayListPictureProcessingEventActivity.ExecuteEndpointName, + new PublishPlayListPictureProcessingEventArguments(message.PlayListId, message.Bucket, message.PictureDestinationKey, message.Sizes)); + + private static RoutingSlipBuilder BuildSlip( + Guid subjectId, + Guid pictureIntentId, + string bucket, + string pictureSourceKey, + string pictureDestinationKey, + Guid? correlationId, + string processKind, + string publishStepName, + string publishEndpointName, + object publishArguments) + { + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.CopyPictureToFinal, + CopyFileInBucketActivity.ExecuteEndpointName, + new CopyFileInBucketArguments(bucket, pictureSourceKey, bucket, pictureDestinationKey)) + .AddStep( + ActivityNames.ConsumeUploadIntents, + ConsumeUploadIntentsActivity.ExecuteEndpointName, + new ConsumeUploadIntentsArguments([pictureIntentId])) + .AddStep(publishStepName, publishEndpointName, publishArguments) + .TrackFaults(subjectId, processKind); + + builder.AddVariable(RoutingSlipVariableNames.Workflow.Bucket, bucket); + builder.AddVariable(RoutingSlipVariableNames.Workflow.PictureKey, pictureDestinationKey); + + return builder; + } +} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs index ba6fa48..bf81fe6 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs @@ -1,176 +1,86 @@ using MassTransit; -using MassTransit.Courier.Contracts; using Musify.Application.Events; using Musify.Application.Shared; using Musify.Infrastructure.Configuration; using Musify.Infrastructure.MassTransit.Activities.Files; using Musify.Infrastructure.MassTransit.Activities.Pictures; using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; +/// Downloads a picture, resizes it to the three sizes, uploads them and records the result on the owner. public class PictureWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration) { - public RoutingSlipBuilder Build(UpdateTrackPictureEvent message, Guid? correlationId) - { - var routingSlipBuilder = BuildPictureWorkflow( - message.Bucket, - message.SourceKey, - message.Sizes, - correlationId); + public RoutingSlipBuilder Build(UpdateTrackPictureEvent message, Guid? correlationId) => + BuildPictureWorkflow( + message.TrackId, message.Bucket, message.SourceKey, message.Sizes, correlationId, + ActivityNames.UpdateTrackPicture, UpdateTrackPictureActivity.ExecuteEndpointName, + RoutingSlipVariableNames.ProcessKinds.TrackPicture); - routingSlipBuilder.AddActivity( - ActivityNames.UpdateTrackPicture, - EndpointHelper.BuildExecuteActivityUri(UpdateTrackPictureActivity.ExecuteEndpointName), - new UpdateTrackPictureArguments( - message.TrackId, - message.SourceKey, - RoutingSlipVariableNames.Picture.SmallResizedFilePath, - RoutingSlipVariableNames.Picture.MediumResizedFilePath, - RoutingSlipVariableNames.Picture.LargeResizedFilePath)); + public RoutingSlipBuilder Build(UpdatePlayListPictureEvent message, Guid? correlationId) => + BuildPictureWorkflow( + message.PlayListId, message.Bucket, message.SourceKey, message.Sizes, correlationId, + ActivityNames.UpdatePlayListPicture, UpdatePlayListPictureActivity.ExecuteEndpointName, + RoutingSlipVariableNames.ProcessKinds.PlayListPicture); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.TrackId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.TrackPicture); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } - - public RoutingSlipBuilder Build(UpdatePlayListPictureEvent message, Guid? correlationId) - { - var routingSlipBuilder = BuildPictureWorkflow( - message.Bucket, - message.SourceKey, - message.Sizes, - correlationId); - - routingSlipBuilder.AddActivity( - ActivityNames.UpdatePlayListPicture, - EndpointHelper.BuildExecuteActivityUri(UpdatePlayListPictureActivity.ExecuteEndpointName), - new UpdatePlayListPictureArguments( - message.PlayListId, - message.SourceKey, - RoutingSlipVariableNames.Picture.SmallResizedFilePath, - RoutingSlipVariableNames.Picture.MediumResizedFilePath, - RoutingSlipVariableNames.Picture.LargeResizedFilePath)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.PlayListId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.PlayListPicture); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } - - public RoutingSlipBuilder Build(UpdateAlbumPictureEvent message, Guid? correlationId) - { - var routingSlipBuilder = BuildPictureWorkflow( - message.Bucket, - message.SourceKey, - message.Sizes, - correlationId); - - routingSlipBuilder.AddActivity( - ActivityNames.UpdateAlbumPicture, - EndpointHelper.BuildExecuteActivityUri(UpdateAlbumPictureActivity.ExecuteEndpointName), - new UpdateAlbumPictureArguments( - message.AlbumId, - message.SourceKey, - RoutingSlipVariableNames.Picture.SmallResizedFilePath, - RoutingSlipVariableNames.Picture.MediumResizedFilePath, - RoutingSlipVariableNames.Picture.LargeResizedFilePath)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.AlbumId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.AlbumPicture); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } + public RoutingSlipBuilder Build(UpdateAlbumPictureEvent message, Guid? correlationId) => + BuildPictureWorkflow( + message.AlbumId, message.Bucket, message.SourceKey, message.Sizes, correlationId, + ActivityNames.UpdateAlbumPicture, UpdateAlbumPictureActivity.ExecuteEndpointName, + RoutingSlipVariableNames.ProcessKinds.AlbumPicture); private RoutingSlipBuilder BuildPictureWorkflow( + Guid subjectId, string bucket, string sourceKey, ImageSizes sizes, - Guid? correlationId) + Guid? correlationId, + string updateStepName, + string updateEndpointName, + string processKind) { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); + var builder = RoutingSlips.Create(correlationId) + .AddStep( + ActivityNames.GeneratePictureWorkflowPaths, + GeneratePictureWorkflowPathsActivity.ExecuteEndpointName, + new GeneratePictureWorkflowPathsArguments(workerConfiguration.Routes.TemporaryFilesDirectory, sourceKey)) + .AddStep( + ActivityNames.DownloadFile, + DownloadFileFromBucketActivity.ExecuteEndpointName, + new DownloadFileFromBucketArguments(bucket, sourceKey, RoutingSlipVariableNames.Picture.OriginalFilePath)); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); + AddResize(builder, ActivityNames.ResizeSmall, ActivityNames.UploadSmall, bucket, RoutingSlipVariableNames.Picture.SmallResizedFilePath, sizes.Small); + AddResize(builder, ActivityNames.ResizeMedium, ActivityNames.UploadMedium, bucket, RoutingSlipVariableNames.Picture.MediumResizedFilePath, sizes.Medium); + AddResize(builder, ActivityNames.ResizeLarge, ActivityNames.UploadLarge, bucket, RoutingSlipVariableNames.Picture.LargeResizedFilePath, sizes.Large); - routingSlipBuilder.AddActivity( - ActivityNames.GeneratePictureWorkflowPaths, - EndpointHelper.BuildExecuteActivityUri(GeneratePictureWorkflowPathsActivity.ExecuteEndpointName), - new GeneratePictureWorkflowPathsArguments( - workerConfiguration.Routes.TemporaryFilesDirectory, - sourceKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.DownloadFile, - EndpointHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName), - new DownloadFileFromBucketArguments( - bucket, - sourceKey, - RoutingSlipVariableNames.Picture.OriginalFilePath)); - - AddResizeActivity( - routingSlipBuilder, - ActivityNames.ResizeSmall, - ActivityNames.UploadSmall, - bucket, - RoutingSlipVariableNames.Picture.SmallResizedFilePath, - sizes.Small); - - AddResizeActivity( - routingSlipBuilder, - ActivityNames.ResizeMedium, - ActivityNames.UploadMedium, - bucket, - RoutingSlipVariableNames.Picture.MediumResizedFilePath, - sizes.Medium); - - AddResizeActivity( - routingSlipBuilder, - ActivityNames.ResizeLarge, - ActivityNames.UploadLarge, - bucket, - RoutingSlipVariableNames.Picture.LargeResizedFilePath, - sizes.Large); - - return routingSlipBuilder; + return builder + .AddStep( + updateStepName, + updateEndpointName, + new UpdatePicturesArguments( + subjectId, + sourceKey, + RoutingSlipVariableNames.Picture.SmallResizedFilePath, + RoutingSlipVariableNames.Picture.MediumResizedFilePath, + RoutingSlipVariableNames.Picture.LargeResizedFilePath)) + .TrackFaults(subjectId, processKind); } - private static void AddResizeActivity( - RoutingSlipBuilder routingSlipBuilder, - string resizeActivityName, - string uploadActivityName, - string bucketName, - string destinationFilePathVariableName, - ImageSize imageSize) - { - routingSlipBuilder.AddActivity( - resizeActivityName, - EndpointHelper.BuildExecuteActivityUri(ResizePictureActivity.ExecuteEndpointName), - new ResizePictureLocalArguments( - RoutingSlipVariableNames.Picture.OriginalFilePath, - destinationFilePathVariableName, - imageSize.Width, - imageSize.Height)); - - routingSlipBuilder.AddActivity( - uploadActivityName, - EndpointHelper.BuildExecuteActivityUri(UploadFileToBucketActivity.ExecuteEndpointName), - new UploadFileToBucketArguments( - destinationFilePathVariableName, - bucketName, - imageSize.SavePath)); - } + private static void AddResize( + RoutingSlipBuilder builder, + string resizeStepName, + string uploadStepName, + string bucket, + string resizedFileVariable, + ImageSize size) => + builder + .AddStep( + resizeStepName, + ResizePictureActivity.ExecuteEndpointName, + new ResizePictureLocalArguments( + RoutingSlipVariableNames.Picture.OriginalFilePath, resizedFileVariable, size.Width, size.Height)) + .AddStep( + uploadStepName, + UploadFileToBucketActivity.ExecuteEndpointName, + new UploadFileToBucketArguments(resizedFileVariable, bucket, size.SavePath)); } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PlayListPictureSourceRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PlayListPictureSourceRoutingSlipBuilder.cs deleted file mode 100644 index 6744749..0000000 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/PlayListPictureSourceRoutingSlipBuilder.cs +++ /dev/null @@ -1,87 +0,0 @@ -using MassTransit; -using MassTransit.Courier.Contracts; -using Musify.Application.Events; -using Musify.Application.Shared; -using Musify.Infrastructure.MassTransit.Activities.Files; -using Musify.Infrastructure.MassTransit.Activities.PlayLists; -using Musify.Infrastructure.MassTransit.Activities.UploadIntents; -using Musify.Infrastructure.MassTransit.Arguments; -using Musify.Infrastructure.MassTransit.Consumers; - -namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; - -public class PlayListPictureSourceRoutingSlipBuilder -{ - public RoutingSlipBuilder Build(CreatePlayListResourcesEvent message, Guid? correlationId) - => BuildSlip( - message.PlayListId, - message.PictureIntentId, - message.Bucket, - message.PictureSourceKey, - message.PictureDestinationKey, - message.Sizes, - correlationId, - RoutingSlipVariableNames.ProcessKinds.PlayListCreation); - - public RoutingSlipBuilder Build(UpdatePlayListPictureSourceEvent message, Guid? correlationId) - => BuildSlip( - message.PlayListId, - message.PictureIntentId, - message.Bucket, - message.PictureSourceKey, - message.PictureDestinationKey, - message.Sizes, - correlationId, - RoutingSlipVariableNames.ProcessKinds.PlayListPicture); - - private static RoutingSlipBuilder BuildSlip( - Guid playListId, - Guid pictureIntentId, - string bucket, - string pictureSourceKey, - string pictureDestinationKey, - ImageSizes sizes, - Guid? correlationId, - string processKind) - { - var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); - - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName), - RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); - - routingSlipBuilder.AddActivity( - ActivityNames.CopyPictureToFinal, - EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName), - new CopyFileInBucketArguments( - bucket, - pictureSourceKey, - bucket, - pictureDestinationKey)); - - routingSlipBuilder.AddActivity( - ActivityNames.ConsumeUploadIntents, - EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName), - new ConsumeUploadIntentsArguments([pictureIntentId])); - - routingSlipBuilder.AddActivity( - ActivityNames.PublishPlayListPictureProcessingEvent, - EndpointHelper.BuildExecuteActivityUri(PublishPlayListPictureProcessingEventActivity.ExecuteEndpointName), - new PublishPlayListPictureProcessingEventArguments( - playListId, - bucket, - pictureDestinationKey, - sizes)); - - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, playListId); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, processKind); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.Bucket, bucket); - routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.PictureKey, pictureDestinationKey); - routingSlipBuilder.AddSubscription( - EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName), - RoutingSlipEvents.Faulted); - - return routingSlipBuilder; - } -} diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlipVariableNames.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlipVariableNames.cs index 6d49e2e..393aea4 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlipVariableNames.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlipVariableNames.cs @@ -1,4 +1,4 @@ -namespace Musify.Infrastructure.MassTransit; +namespace Musify.Infrastructure.MassTransit.RoutingSlip; internal static class RoutingSlipVariableNames { diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlips.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlips.cs new file mode 100644 index 0000000..7134165 --- /dev/null +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/RoutingSlips.cs @@ -0,0 +1,48 @@ +using MassTransit; +using MassTransit.Courier.Contracts; +using Musify.Infrastructure.MassTransit.Activities.Files; +using Musify.Infrastructure.MassTransit.Arguments; +using Musify.Infrastructure.MassTransit.Consumers; + +namespace Musify.Infrastructure.MassTransit.RoutingSlip; + +/// Shared building blocks for every routing slip in the worker. +internal static class RoutingSlips +{ + public static Uri ExecuteUri(string endpointName) => new($"queue:{endpointName}_execute"); + + public static Uri ConsumerUri(string queueName) => new($"queue:{queueName}"); + + /// A new slip that carries the correlation id and always cleans its temporary directory up. + public static RoutingSlipBuilder Create(Guid? correlationId) + { + var builder = new RoutingSlipBuilder(NewId.NextGuid()); + builder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty); + builder.AddSubscription( + ConsumerUri(RoutingSlipCleanUpConsumer.QueueName), + RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted); + + return builder; + } + + public static RoutingSlipBuilder AddStep( + this RoutingSlipBuilder builder, string name, string executeEndpointName, object arguments) + { + builder.AddActivity(name, ExecuteUri(executeEndpointName), arguments); + return builder; + } + + /// Publishes a failure event for the subject when the slip faults. + public static RoutingSlipBuilder TrackFaults( + this RoutingSlipBuilder builder, Guid subjectId, string processKind) + { + builder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, subjectId); + builder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, processKind); + builder.AddSubscription(ConsumerUri(ProcessingSlipFaultConsumer.QueueName), RoutingSlipEvents.Faulted); + return builder; + } + + public static RoutingSlipBuilder AddRemoveFile( + this RoutingSlipBuilder builder, string stepName, string bucket, string key) => + builder.AddStep(stepName, RemoveFileFromBucketActivity.ExecuteEndpointName, new RemoveFileFromBucketArguments(bucket, key)); +}