diff --git a/Application/Configuration/PlayListConfiguration.cs b/Application/Configuration/PlayListConfiguration.cs
index 2eccd32..7992eaa 100644
--- a/Application/Configuration/PlayListConfiguration.cs
+++ b/Application/Configuration/PlayListConfiguration.cs
@@ -30,6 +30,9 @@ namespace Musify.Application.Configuration
[Required]
public string LargePictures { get; set; } = "LargePictures";
+ [Required]
+ public string PresetOriginalPicture { get; set; } = "PresetOriginalPicture.webp";
+
[Required]
public string PresetSmallPicture { get; set; } = "PresetSmallPicture.webp";
diff --git a/Application/Configuration/TrackConfiguration.cs b/Application/Configuration/TrackConfiguration.cs
index dc135f7..4d9d7a8 100644
--- a/Application/Configuration/TrackConfiguration.cs
+++ b/Application/Configuration/TrackConfiguration.cs
@@ -30,6 +30,9 @@ namespace Musify.Application.Configuration
[Required]
public string LargePictures { get; set; } = "LargeTracks";
+ [Required]
+ public string PresetOriginalPicture { get; set; } = "PresetOriginalPicture.webp";
+
[Required]
public string PresetSmallPicture { get; set; } = "PresetSmallPicture.webp";
diff --git a/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs b/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs
index 7554c14..510e1f3 100644
--- a/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs
+++ b/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs
@@ -32,6 +32,7 @@ namespace Musify.Application.PlayLists.Handlers
Name = request.Name,
NormalizedName = request.Name.Trim().ToUpperInvariant(),
Description = request.Description,
+ OriginalPictureKeyName = playListConfiguration.Routes.PresetOriginalPicture,
SmallPictureKeyName = playListConfiguration.Routes.PresetSmallPicture,
MediumPictureKeyName = playListConfiguration.Routes.PresetMediumPicture,
LargePictureKeyName = playListConfiguration.Routes.PresetLargePicture,
@@ -65,6 +66,8 @@ namespace Musify.Application.PlayLists.Handlers
playList.Id, string.Join("; ", uploadResult.Errors));
return Result.Error($"Failed to upload picture for PlayList with Id {playList.Id} to storage.");
}
+
+ playList.OriginalPictureKeyName = pictureStorageKey;
logger.LogInformation("Playlist image uploaded successfully for PlayList with Id={PlayListId}.", playList.Id);
try
diff --git a/Application/Tracks/Handler/CreateTrackCommandHandler.cs b/Application/Tracks/Handler/CreateTrackCommandHandler.cs
index 2270ddc..8124cd1 100644
--- a/Application/Tracks/Handler/CreateTrackCommandHandler.cs
+++ b/Application/Tracks/Handler/CreateTrackCommandHandler.cs
@@ -30,6 +30,7 @@ namespace Musify.Application.Tracks.Handler
{
Title = request.Title,
NormalizedTitle = request.Title.Trim().ToUpperInvariant(),
+ OriginalPictureKeyName = originalPictureKeyName,
SmallPictureKeyName = trackConfiguration.Routes.PresetSmallPicture,
MediumPictureKeyName = trackConfiguration.Routes.PresetMediumPicture,
LargePictureKeyName = trackConfiguration.Routes.PresetLargePicture,
diff --git a/Domain/Entities/PlayList.cs b/Domain/Entities/PlayList.cs
index 44d751f..876854e 100644
--- a/Domain/Entities/PlayList.cs
+++ b/Domain/Entities/PlayList.cs
@@ -25,6 +25,10 @@ namespace Musify.Domain.Entities
[MaxLength(254)]
public required string Description { get; set; } = "No description was provided.";
+ [Required]
+ [MaxLength(64)]
+ public required string OriginalPictureKeyName { get; set; }
+
[Required]
[MaxLength(64)]
public required string SmallPictureKeyName { get; set; }
diff --git a/Domain/Entities/Track.cs b/Domain/Entities/Track.cs
index 8986fdd..888dfd6 100644
--- a/Domain/Entities/Track.cs
+++ b/Domain/Entities/Track.cs
@@ -16,6 +16,10 @@ namespace Musify.Domain.Entities
[MaxLength(50)]
public required string NormalizedTitle { get; set; }
+ [Required]
+ [MaxLength(64)]
+ public required string OriginalPictureKeyName { get; set; }
+
[Required]
[MaxLength(64)]
public required string SmallPictureKeyName { get; set; }
diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj
index d26a09e..61aa300 100644
--- a/Infrastructure/Infrastructure.csproj
+++ b/Infrastructure/Infrastructure.csproj
@@ -32,4 +32,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs b/Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs
new file mode 100644
index 0000000..aecdd9a
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs
@@ -0,0 +1,63 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class GenerateAudioWorkflowPathsActivity(
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IExecuteActivity
+ {
+ public const string ExecuteEndpointName = "Generate-Audio-Workflow-Paths";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(GenerateAudioWorkflowPathsActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ try
+ {
+ var folderName = Guid.NewGuid().ToString();
+ var sourceFileName = Path.GetFileName(executeContext.Arguments.SourceKeyName);
+ var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, folderName);
+ var sourceFilePath = Path.Combine(workingDirectory, sourceFileName);
+
+ Directory.CreateDirectory(workingDirectory);
+
+ logger.LogDebug("Generated audio workflow paths. WorkingDirectory: {WorkingDirectory}, SourceFilePath: {SourceFilePath}",
+ workingDirectory,
+ sourceFilePath);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.CompletedWithVariables(new Dictionary
+ {
+ [RoutingSlipVariableNames.Workflow.TempDirectory] = workingDirectory,
+ [RoutingSlipVariableNames.Audio.WorkingDirectory] = workingDirectory,
+ [RoutingSlipVariableNames.Audio.SourceFilePath] = sourceFilePath,
+ [RoutingSlipVariableNames.Audio.TranscodedDirectory] = workingDirectory
+ });
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error generating audio workflow paths for key {SourceKeyName}",
+ executeContext.Arguments.SourceKeyName);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+ }
+}
diff --git a/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs b/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs
new file mode 100644
index 0000000..4856b8b
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs
@@ -0,0 +1,89 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class TranscodeDashAudioActivity(
+ IAudioTranscoder audioTranscoder,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Transcode-Dash-Audio";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(TranscodeDashAudioActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ var sourceFilePath = executeContext.GetVariable(executeContext.Arguments.SourceFilePathVariableName);
+ var workingDirectory = executeContext.GetVariable(executeContext.Arguments.WorkingDirectoryVariableName);
+
+ if (string.IsNullOrWhiteSpace(sourceFilePath) || string.IsNullOrWhiteSpace(workingDirectory))
+ {
+ throw new InvalidOperationException("Transcode activity requires source file path and working directory variables.");
+ }
+
+ try
+ {
+ await using var fileStream = File.OpenRead(sourceFilePath);
+
+ var result = await audioTranscoder.TranscodeToDashAsync(
+ fileStream,
+ workingDirectory,
+ executeContext.CancellationToken);
+
+ if (!result.IsSuccess)
+ {
+ var errorMessage = string.Join("; ", result.Errors);
+ logger.LogError("Transcoding failed for file {SourceFilePath}. Errors: {ErrorMessage}",
+ sourceFilePath, errorMessage);
+ throw new Exception(errorMessage);
+ }
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error during transcoding activity for file {SourceFilePath}",
+ sourceFilePath);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ if (Directory.Exists(compensateContext.Log.WorkingDirectory))
+ {
+ Directory.Delete(compensateContext.Log.WorkingDirectory, true);
+ }
+
+ return Task.FromResult(compensateContext.Compensated());
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating transcoded audio directory {WorkingDirectory}", compensateContext.Log.WorkingDirectory);
+ return Task.FromResult(compensateContext.Failed(exception));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs
new file mode 100644
index 0000000..d64b602
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs
@@ -0,0 +1,100 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class DownloadFileFromBucketActivity(
+ IStorageHandler storageHandler,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Download-File-From-Bucket";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(DownloadFileFromBucketActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ var destinationPath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariableName);
+ if (string.IsNullOrWhiteSpace(destinationPath))
+ {
+ throw new InvalidOperationException(
+ $"Download activity requires variable '{executeContext.Arguments.DestinationFilePathVariableName}' with destination file path.");
+ }
+
+ try
+ {
+ var getFile = await storageHandler.GetFileAsync(
+ executeContext.Arguments.BucketName,
+ executeContext.Arguments.KeyName,
+ executeContext.CancellationToken);
+
+ if (!getFile.IsSuccess)
+ {
+ var errorMessage = string.Join("; ", getFile.Errors);
+ logger.LogWarning("Failed to download file from bucket. Bucket: {BucketName}, Key: {KeyName}, Errors: {Errors}",
+ executeContext.Arguments.BucketName,
+ executeContext.Arguments.KeyName,
+ errorMessage);
+ throw new Exception(errorMessage);
+ }
+
+ var destinationDirectory = Path.GetDirectoryName(destinationPath);
+ if (!string.IsNullOrWhiteSpace(destinationDirectory))
+ {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+
+ using var fileStream = getFile.Value;
+ if (fileStream.CanSeek)
+ fileStream.Position = 0;
+
+ using var destinationStream = File.Create(destinationPath);
+ await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error downloading file from bucket");
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ if (File.Exists(compensateContext.Log.DestinationFilePath))
+ {
+ File.Delete(compensateContext.Log.DestinationFilePath);
+ }
+
+ return Task.FromResult(compensateContext.Compensated());
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating downloaded file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
+ return Task.FromResult(compensateContext.Failed(exception));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/RemoveFileFromBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs
similarity index 100%
rename from Infrastructure/Messaging/Activities/RemoveFileFromBucketActivity.cs
rename to Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs
diff --git a/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucket.cs b/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucket.cs
new file mode 100644
index 0000000..ace2c4e
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucket.cs
@@ -0,0 +1,109 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class TransferFilesToBucket(
+ IStorageHandler storageHandler,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Transfer-Files-To-Bucket";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(TransferFilesToBucket),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ var folderPath = executeContext.GetVariable(executeContext.Arguments.SourceDirectoryVariableName);
+ if (string.IsNullOrWhiteSpace(folderPath))
+ {
+ throw new InvalidOperationException(
+ $"Transfer activity requires variable '{executeContext.Arguments.SourceDirectoryVariableName}' with source directory path.");
+ }
+
+ var destinationKeyName = executeContext.Arguments.DestinationKeyName;
+ if (string.IsNullOrWhiteSpace(destinationKeyName) && !string.IsNullOrWhiteSpace(executeContext.Arguments.DestinationKeyNameVariableName))
+ {
+ destinationKeyName = executeContext.GetVariable(executeContext.Arguments.DestinationKeyNameVariableName);
+ }
+
+ if (string.IsNullOrWhiteSpace(destinationKeyName))
+ {
+ throw new InvalidOperationException("Transfer activity requires a destination key name from arguments or variables.");
+ }
+
+ try
+ {
+ var uploadedKeys = Directory.GetFiles(folderPath)
+ .Select(file => $"{destinationKeyName}/{Path.GetFileName(file)}")
+ .ToArray();
+
+ var transferFilesResult = await storageHandler.TransferFilesAsync(
+ folderPath,
+ executeContext.Arguments.DestinationBucketName,
+ destinationKeyName,
+ executeContext.CancellationToken);
+
+ if (!transferFilesResult.IsSuccess)
+ {
+ var errorMessage = string.Join("; ", transferFilesResult.Errors);
+ logger.LogWarning("Failed to transfer files from {FolderPath} to bucket {DestinationBucketName}, key {DestinationKeyName}. Errors: {Errors}",
+ folderPath,
+ executeContext.Arguments.DestinationBucketName,
+ destinationKeyName,
+ errorMessage);
+ throw new Exception(errorMessage);
+ }
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error transferring files from {FolderPath} to bucket {DestinationBucketName}",
+ folderPath,
+ executeContext.Arguments.DestinationBucketName);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public async Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ foreach (var uploadedKey in compensateContext.Log.UploadedKeys)
+ {
+ await storageHandler.RemoveFileAsync(
+ compensateContext.Log.DestinationBucketName,
+ uploadedKey,
+ compensateContext.CancellationToken);
+ }
+
+ return compensateContext.Compensated();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating transferred files to bucket {DestinationBucketName}", compensateContext.Log.DestinationBucketName);
+ return compensateContext.Failed(exception);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs b/Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs
new file mode 100644
index 0000000..51dfc21
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs
@@ -0,0 +1,76 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class GeneratePictureWorkflowPathsActivity(
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IExecuteActivity
+ {
+ public const string ExecuteEndpointName = "Generate-Picture-Workflow-Paths";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(GeneratePictureWorkflowPathsActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ try
+ {
+ var destinationFolderName = Guid.NewGuid().ToString();
+ var originalFileName = Path.GetFileName(executeContext.Arguments.SourceKeyName);
+ var sourceFileExtension = Path.GetExtension(originalFileName);
+ var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, destinationFolderName);
+
+
+ var sourceFileName = Guid.NewGuid().ToString() + sourceFileExtension;
+ var smallPictureFileName = Guid.NewGuid().ToString() + sourceFileExtension;
+ var mediumPictureFileName = Guid.NewGuid().ToString() + sourceFileExtension;
+ var largePictureFileName = Guid.NewGuid().ToString() + sourceFileExtension;
+
+ var sourceFilePath = Path.Combine(workingDirectory, sourceFileName);
+ var smallPictureFilePath = Path.Combine(workingDirectory, smallPictureFileName);
+ var mediumPictureFilePath = Path.Combine(workingDirectory, mediumPictureFileName);
+ var largePictureFilePath = Path.Combine(workingDirectory, largePictureFileName);
+
+ Directory.CreateDirectory(workingDirectory);
+
+ logger.LogDebug("Generated picture workflow paths. WorkingDirectory: {WorkingDirectory}, SourceFilePath: {SourceFilePath}",
+ workingDirectory,
+ sourceFilePath);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.CompletedWithVariables(new Dictionary
+ {
+ [RoutingSlipVariableNames.Workflow.TempDirectory] = workingDirectory,
+ [RoutingSlipVariableNames.Picture.OriginalFilePath] = sourceFilePath,
+ [RoutingSlipVariableNames.Picture.SmallResizedFilePath] = smallPictureFilePath,
+ [RoutingSlipVariableNames.Picture.MediumResizedFilePath] = mediumPictureFilePath,
+ [RoutingSlipVariableNames.Picture.LargeResizedFilePath] = largePictureFilePath,
+ [RoutingSlipVariableNames.Picture.DestinationFolderName] = destinationFolderName
+ } );
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error generating picture workflow paths for key {SourceKeyName}",
+ executeContext.Arguments.SourceKeyName);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs b/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs
new file mode 100644
index 0000000..975d52b
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs
@@ -0,0 +1,113 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class ResizePictureActivity(
+ IPictureHandler pictureHandler,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Resize-Picture";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(ResizePictureActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ var sourceFilePath = executeContext.GetVariable(executeContext.Arguments.SourceFilePathVariableName);
+ var destinationFilePath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariableName);
+
+ if (string.IsNullOrWhiteSpace(sourceFilePath) || string.IsNullOrWhiteSpace(destinationFilePath))
+ {
+ throw new InvalidOperationException("Resize activity requires source and destination file path variables.");
+ }
+
+ try
+ {
+ if (!File.Exists(sourceFilePath))
+ {
+ logger.LogWarning("Source file not found at {SourceFilePath}", sourceFilePath);
+ throw new FileNotFoundException($"Source file not found: {sourceFilePath}");
+ }
+
+ await using var fileStream = File.OpenRead(sourceFilePath);
+
+ var resizedPicture = await pictureHandler.ResizePictureAsync(
+ fileStream,
+ executeContext.Arguments.Width,
+ executeContext.Arguments.Height,
+ executeContext.CancellationToken);
+
+ if (!resizedPicture.IsSuccess)
+ {
+ var errorMessage = string.Join("; ", resizedPicture.Errors);
+ logger.LogError("Failed to resize picture {SourceFilePath} to {Width}x{Height}. Errors: {Errors}",
+ sourceFilePath,
+ executeContext.Arguments.Width,
+ executeContext.Arguments.Height,
+ errorMessage);
+ throw new Exception(errorMessage);
+ }
+
+ var destinationDirectory = Path.GetDirectoryName(destinationFilePath);
+ if (!string.IsNullOrEmpty(destinationDirectory))
+ {
+ Directory.CreateDirectory(destinationDirectory);
+ }
+
+ await using var destinationStream = File.Create(destinationFilePath);
+ await resizedPicture.Value.CopyToAsync(destinationStream, executeContext.CancellationToken);
+
+ logger.LogDebug("Picture resized from {SourceFilePath} to {DestinationFilePath} ({Width}x{Height})",
+ sourceFilePath,
+ destinationFilePath,
+ executeContext.Arguments.Width,
+ executeContext.Arguments.Height);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error resizing picture {SourceFilePath}", sourceFilePath);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ if (File.Exists(compensateContext.Log.DestinationFilePath))
+ {
+ File.Delete(compensateContext.Log.DestinationFilePath);
+ }
+
+ return Task.FromResult(compensateContext.Compensated());
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating resized file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
+ return Task.FromResult(compensateContext.Failed(exception));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs b/Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs
new file mode 100644
index 0000000..bf44932
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs
@@ -0,0 +1,107 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class UpdatePlayListPictureActivity(
+ IDatabase database,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Update-PlayList-Picture";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(UpdatePlayListPictureActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ try
+ {
+ var playList = await database.PlayLists.FindAsync(
+ [executeContext.Arguments.PlayListId],
+ cancellationToken: executeContext.CancellationToken);
+
+ if (playList is null)
+ {
+ logger.LogWarning("PlayList with id {PlayListId} not found",
+ executeContext.Arguments.PlayListId);
+ throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found");
+ }
+
+ var log = new UpdatePlayListPictureLog(
+ playList.Id,
+ playList.OriginalPictureKeyName,
+ playList.SmallPictureKeyName,
+ playList.MediumPictureKeyName,
+ playList.LargePictureKeyName);
+
+ playList.OriginalPictureKeyName = executeContext.Arguments.OriginalPictureKeyName;
+ playList.SmallPictureKeyName = executeContext.Arguments.SmallPictureKeyName;
+ playList.MediumPictureKeyName = executeContext.Arguments.MediumPictureKeyName;
+ playList.LargePictureKeyName = executeContext.Arguments.LargePictureKeyName;
+
+ database.PlayLists.Update(playList);
+ await database.SaveChangesAsync(executeContext.CancellationToken);
+
+ logger.LogInformation("Updated PlayList {PlayListId} pictures",
+ executeContext.Arguments.PlayListId);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error updating PlayList {PlayListId} pictures",
+ executeContext.Arguments.PlayListId);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public async Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ var playList = await database.PlayLists.FindAsync(
+ [compensateContext.Log.PlayListId],
+ cancellationToken: compensateContext.CancellationToken);
+
+ if (playList is null)
+ {
+ return compensateContext.Compensated();
+ }
+
+ playList.OriginalPictureKeyName = compensateContext.Log.PreviousOriginalPictureKeyName;
+ playList.SmallPictureKeyName = compensateContext.Log.PreviousSmallPictureKeyName;
+ playList.MediumPictureKeyName = compensateContext.Log.PreviousMediumPictureKeyName;
+ playList.LargePictureKeyName = compensateContext.Log.PreviousLargePictureKeyName;
+
+ database.PlayLists.Update(playList);
+ await database.SaveChangesAsync(compensateContext.CancellationToken);
+
+ return compensateContext.Compensated();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating PlayList {PlayListId} pictures", compensateContext.Log.PlayListId);
+ return compensateContext.Failed(exception);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs b/Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs
new file mode 100644
index 0000000..88a0832
--- /dev/null
+++ b/Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs
@@ -0,0 +1,107 @@
+using MassTransit;
+using Microsoft.Extensions.Logging;
+using Musify.Application.Contracts.Infrastructure;
+using Musify.Domain.Entities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+
+namespace Musify.Infrastructure.Messaging.Activities
+{
+ public class UpdateTrackPictureActivity(
+ IDatabase database,
+ ILogger logger,
+ IProcessTrackingStore processTrackingStore)
+ : IActivity
+ {
+ public const string ExecuteEndpointName = "Update-Track-Picture";
+
+ public async Task Execute(ExecuteContext executeContext)
+ {
+ var processId = await processTrackingStore.GetOrCreateProcessAsync(
+ "RoutingSlip",
+ executeContext.CorrelationId ?? executeContext.TrackingNumber,
+ executeContext.ConversationId,
+ executeContext.MessageId,
+ executeContext.CancellationToken);
+
+ var stepId = await processTrackingStore.StartStepAsync(
+ processId,
+ nameof(UpdateTrackPictureActivity),
+ ProcessStepComponentType.Activity,
+ 0,
+ executeContext.CancellationToken);
+
+ try
+ {
+ var track = await database.Tracks.FindAsync(
+ [executeContext.Arguments.TrackId],
+ cancellationToken: executeContext.CancellationToken);
+
+ if (track is null)
+ {
+ logger.LogWarning("Track with id {TrackId} not found",
+ executeContext.Arguments.TrackId);
+ throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
+ }
+
+ var log = new UpdateTrackPictureLog(
+ track.Id,
+ track.OriginalPictureKeyName,
+ track.SmallPictureKeyName,
+ track.MediumPictureKeyName,
+ track.LargePictureKeyName);
+
+ track.OriginalPictureKeyName = executeContext.Arguments.OriginalPictureKeyName;
+ track.SmallPictureKeyName = executeContext.Arguments.SmallPictureKeyName;
+ track.MediumPictureKeyName = executeContext.Arguments.MediumPictureKeyName;
+ track.LargePictureKeyName = executeContext.Arguments.LargePictureKeyName;
+
+ database.Tracks.Update(track);
+ await database.SaveChangesAsync(executeContext.CancellationToken);
+
+ logger.LogInformation("Updated Track {TrackId} pictures",
+ executeContext.Arguments.TrackId);
+
+ await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
+ return executeContext.Completed();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error updating Track {TrackId} pictures",
+ executeContext.Arguments.TrackId);
+ await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
+ throw;
+ }
+ }
+
+ public async Task Compensate(CompensateContext compensateContext)
+ {
+ try
+ {
+ var track = await database.Tracks.FindAsync(
+ [compensateContext.Log.TrackId],
+ cancellationToken: compensateContext.CancellationToken);
+
+ if (track is null)
+ {
+ return compensateContext.Compensated();
+ }
+
+ track.OriginalPictureKeyName = compensateContext.Log.PreviousOriginalPictureKeyName;
+ track.SmallPictureKeyName = compensateContext.Log.PreviousSmallPictureKeyName;
+ track.MediumPictureKeyName = compensateContext.Log.PreviousMediumPictureKeyName;
+ track.LargePictureKeyName = compensateContext.Log.PreviousLargePictureKeyName;
+
+ database.Tracks.Update(track);
+ await database.SaveChangesAsync(compensateContext.CancellationToken);
+
+ return compensateContext.Compensated();
+ }
+ catch (Exception exception)
+ {
+ logger.LogError(exception, "Error compensating Track {TrackId} pictures", compensateContext.Log.TrackId);
+ return compensateContext.Failed(exception);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Arguments/DownloadFileFromBucketArguments.cs b/Infrastructure/MassTransit/Arguments/DownloadFileFromBucketArguments.cs
similarity index 77%
rename from Infrastructure/Messaging/Activities/Arguments/DownloadFileFromBucketArguments.cs
rename to Infrastructure/MassTransit/Arguments/DownloadFileFromBucketArguments.cs
index 3b4ed66..ff0769d 100644
--- a/Infrastructure/Messaging/Activities/Arguments/DownloadFileFromBucketArguments.cs
+++ b/Infrastructure/MassTransit/Arguments/DownloadFileFromBucketArguments.cs
@@ -3,5 +3,5 @@
public record DownloadFileFromBucketArguments(
string BucketName,
string KeyName,
- string DestinationPath);
+ string DestinationFilePathVariableName);
}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Arguments/GenerateAudioWorkflowPathsArguments.cs b/Infrastructure/MassTransit/Arguments/GenerateAudioWorkflowPathsArguments.cs
new file mode 100644
index 0000000..1c74687
--- /dev/null
+++ b/Infrastructure/MassTransit/Arguments/GenerateAudioWorkflowPathsArguments.cs
@@ -0,0 +1,6 @@
+namespace Musify.Infrastructure.Messaging.Activities.Arguments
+{
+ public record GenerateAudioWorkflowPathsArguments(
+ string TemporaryRootDirectory,
+ string SourceKeyName);
+}
diff --git a/Infrastructure/MassTransit/Arguments/GeneratePictureWorkflowPathsArguments.cs b/Infrastructure/MassTransit/Arguments/GeneratePictureWorkflowPathsArguments.cs
new file mode 100644
index 0000000..3fcd2b8
--- /dev/null
+++ b/Infrastructure/MassTransit/Arguments/GeneratePictureWorkflowPathsArguments.cs
@@ -0,0 +1,9 @@
+namespace Musify.Infrastructure.Messaging.Activities.Arguments
+{
+ public record GeneratePictureWorkflowPathsArguments(
+ string TemporaryRootDirectory,
+ string SourceKeyName,
+ string SmallPictureKeyName,
+ string MediumPictureKeyName,
+ string LargePictureKeyName);
+}
diff --git a/Infrastructure/Messaging/Activities/Arguments/RemoveFileFromBucketArguments.cs b/Infrastructure/MassTransit/Arguments/RemoveFileFromBucketArguments.cs
similarity index 100%
rename from Infrastructure/Messaging/Activities/Arguments/RemoveFileFromBucketArguments.cs
rename to Infrastructure/MassTransit/Arguments/RemoveFileFromBucketArguments.cs
diff --git a/Infrastructure/Messaging/Activities/Arguments/ResizePictureLocalArguments.cs b/Infrastructure/MassTransit/Arguments/ResizePictureLocalArguments.cs
similarity index 62%
rename from Infrastructure/Messaging/Activities/Arguments/ResizePictureLocalArguments.cs
rename to Infrastructure/MassTransit/Arguments/ResizePictureLocalArguments.cs
index 3e7ed42..bfd86e8 100644
--- a/Infrastructure/Messaging/Activities/Arguments/ResizePictureLocalArguments.cs
+++ b/Infrastructure/MassTransit/Arguments/ResizePictureLocalArguments.cs
@@ -1,8 +1,8 @@
namespace Musify.Infrastructure.Messaging.Activities.Arguments
{
public record ResizePictureLocalArguments(
- string SourceFilePath,
- string DestinationFilePath,
+ string SourceFilePathVariableName,
+ string DestinationFilePathVariableName,
int Width,
int Height);
}
diff --git a/Infrastructure/MassTransit/Arguments/TranscodeDashAudioArguments.cs b/Infrastructure/MassTransit/Arguments/TranscodeDashAudioArguments.cs
new file mode 100644
index 0000000..00190b8
--- /dev/null
+++ b/Infrastructure/MassTransit/Arguments/TranscodeDashAudioArguments.cs
@@ -0,0 +1,6 @@
+namespace Musify.Infrastructure.Messaging.Activities.Arguments
+{
+ public record TranscodeDashAudioArguments(
+ string SourceFilePathVariableName,
+ string WorkingDirectoryVariableName);
+}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Arguments/TransferFilesToBucketArguments.cs b/Infrastructure/MassTransit/Arguments/TransferFilesToBucketArguments.cs
similarity index 52%
rename from Infrastructure/Messaging/Activities/Arguments/TransferFilesToBucketArguments.cs
rename to Infrastructure/MassTransit/Arguments/TransferFilesToBucketArguments.cs
index b08f97b..5782e6c 100644
--- a/Infrastructure/Messaging/Activities/Arguments/TransferFilesToBucketArguments.cs
+++ b/Infrastructure/MassTransit/Arguments/TransferFilesToBucketArguments.cs
@@ -2,5 +2,7 @@
{
public record TransferFilesToBucketArguments(
string DestinationBucketName,
- string DestinationKeyName);
+ string SourceDirectoryVariableName,
+ string? DestinationKeyName = null,
+ string? DestinationKeyNameVariableName = null);
}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Arguments/UpdatePlayListPictureArguments.cs b/Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs
similarity index 86%
rename from Infrastructure/Messaging/Activities/Arguments/UpdatePlayListPictureArguments.cs
rename to Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs
index 7fbd4bc..8267f58 100644
--- a/Infrastructure/Messaging/Activities/Arguments/UpdatePlayListPictureArguments.cs
+++ b/Infrastructure/MassTransit/Arguments/UpdatePlayListPictureArguments.cs
@@ -2,6 +2,7 @@
{
public record UpdatePlayListPictureArguments(
Guid PlayListId,
+ string OriginalPictureKeyName,
string SmallPictureKeyName,
string MediumPictureKeyName,
string LargePictureKeyName);
diff --git a/Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs b/Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs
similarity index 86%
rename from Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs
rename to Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs
index be1106f..797173d 100644
--- a/Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs
+++ b/Infrastructure/MassTransit/Arguments/UpdateTrackPictureArguments.cs
@@ -2,6 +2,7 @@
{
public record UpdateTrackPictureArguments(
Guid TrackId,
+ string OriginalPictureKeyName,
string SmallPictureKeyName,
string MediumPictureKeyName,
string LargePictureKeyName);
diff --git a/Infrastructure/Messaging/Consumers/MessagingHelper.cs b/Infrastructure/MassTransit/Consumers/MessagingHelper.cs
similarity index 64%
rename from Infrastructure/Messaging/Consumers/MessagingHelper.cs
rename to Infrastructure/MassTransit/Consumers/MessagingHelper.cs
index 5fd9cbb..d08d82a0 100644
--- a/Infrastructure/Messaging/Consumers/MessagingHelper.cs
+++ b/Infrastructure/MassTransit/Consumers/MessagingHelper.cs
@@ -3,5 +3,7 @@ namespace Musify.Infrastructure.Messaging.Consumers
internal static class MessagingHelper
{
internal static Uri BuildExecuteActivityUri(string endpointName) => new($"queue:{endpointName}_execute");
+
+ internal static Uri BuildCompensateActivityUri(string endpointName) => new($"queue:{endpointName}_compensate");
}
}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Consumers/RemoveFileConsumer.cs b/Infrastructure/MassTransit/Consumers/RemoveFileConsumer.cs
new file mode 100644
index 0000000..eb9f79f
--- /dev/null
+++ b/Infrastructure/MassTransit/Consumers/RemoveFileConsumer.cs
@@ -0,0 +1,23 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Messaging.RoutingSlip.Builders;
+
+namespace Musify.Infrastructure.Messaging.Consumers
+{
+ public class RemoveFileConsumer(
+ IBus bus,
+ RemoveFileRoutingSlipBuilder routingSlipBuilder)
+ : IConsumer
+ {
+ public const string QueueName = "Remove-File";
+
+ public async Task Consume(ConsumeContext consumeContext)
+ {
+ var routingSlip = routingSlipBuilder
+ .Build(consumeContext.Message, consumeContext.CorrelationId)
+ .Build();
+
+ await bus.Execute(routingSlip);
+ }
+ }
+}
diff --git a/Infrastructure/MassTransit/Consumers/TranscodeAudioFromTrackConsumer.cs b/Infrastructure/MassTransit/Consumers/TranscodeAudioFromTrackConsumer.cs
new file mode 100644
index 0000000..f4908cc
--- /dev/null
+++ b/Infrastructure/MassTransit/Consumers/TranscodeAudioFromTrackConsumer.cs
@@ -0,0 +1,23 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Messaging.RoutingSlip.Builders;
+
+namespace Musify.Infrastructure.Messaging.Consumers
+{
+ public class TranscodeAudioFromTrackConsumer(
+ IBus bus,
+ AudioWorkflowRoutingSlipBuilder routingSlipBuilder)
+ : IConsumer
+ {
+ public const string QueueName = "Transcode-Audio-From-Track";
+
+ public async Task Consume(ConsumeContext consumeContext)
+ {
+ var routingSlip = routingSlipBuilder
+ .Build(consumeContext.Message, consumeContext.CorrelationId)
+ .Build();
+
+ await bus.Execute(routingSlip);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs b/Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs
new file mode 100644
index 0000000..92b9792
--- /dev/null
+++ b/Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs
@@ -0,0 +1,23 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Messaging.RoutingSlip.Builders;
+
+namespace Musify.Infrastructure.Messaging.Consumers
+{
+ public class UpdatePlayListPictureConsumer(
+ IBus bus,
+ PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
+ : IConsumer
+ {
+ public const string QueueName = "Update-PlayList-Picture";
+
+ public async Task Consume(ConsumeContext consumeContext)
+ {
+ var routingSlip = routingSlipBuilder
+ .Build(consumeContext.Message, consumeContext.CorrelationId)
+ .Build();
+
+ await bus.Execute(routingSlip);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs b/Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs
new file mode 100644
index 0000000..8fba974
--- /dev/null
+++ b/Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs
@@ -0,0 +1,23 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Messaging.RoutingSlip.Builders;
+
+namespace Musify.Infrastructure.Messaging.Consumers
+{
+ public class UpdateTrackPictureConsumer(
+ IBus bus,
+ PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
+ : IConsumer
+ {
+ public const string QueueName = "Update-Track-Picture";
+
+ public async Task Consume(ConsumeContext consumeContext)
+ {
+ var routingSlip = routingSlipBuilder
+ .Build(consumeContext.Message, consumeContext.CorrelationId)
+ .Build();
+
+ await bus.Execute(routingSlip);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/MassTransit/DependencyInjection/MessagingDependencyInjection.cs b/Infrastructure/MassTransit/DependencyInjection/MessagingDependencyInjection.cs
new file mode 100644
index 0000000..4879893
--- /dev/null
+++ b/Infrastructure/MassTransit/DependencyInjection/MessagingDependencyInjection.cs
@@ -0,0 +1,173 @@
+using MassTransit;
+using Microsoft.Extensions.DependencyInjection;
+using Musify.Infrastructure.Configuration;
+using Musify.Infrastructure.Messaging.Activities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+using Musify.Infrastructure.Messaging.Activities.Logs;
+using Musify.Infrastructure.Messaging.Consumers;
+using Musify.Infrastructure.Messaging.Filters;
+using Musify.Infrastructure.Messaging.RoutingSlip.Builders;
+
+namespace Musify.Infrastructure.Messaging
+{
+ public static class MessagingDependencyInjection
+ {
+ public static IServiceCollection AddMassTransitClient(this IServiceCollection serviceDescriptors)
+ {
+ serviceDescriptors.AddMassTransit(options =>
+ {
+ options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
+ ConfigureRabbitMqHost(
+ busFactoryConfigurator,
+ busRegistrationContext.GetRequiredService()));
+ } );
+
+ return serviceDescriptors;
+ }
+
+ public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors)
+ {
+ serviceDescriptors.AddScoped();
+ serviceDescriptors.AddScoped();
+ serviceDescriptors.AddScoped();
+
+ serviceDescriptors.AddMassTransit(options =>
+ {
+ options.AddConsumer();
+ options.AddConsumer();
+ options.AddConsumer();
+ options.AddConsumer();
+
+ options.AddExecuteActivity();
+ options.AddExecuteActivity();
+ options.AddExecuteActivity();
+
+ options.AddActivity();
+ options.AddActivity();
+ options.AddActivity();
+ options.AddActivity();
+ options.AddActivity();
+ options.AddActivity();
+
+ options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
+ {
+ ConfigureRabbitMqHost(
+ busFactoryConfigurator,
+ busRegistrationContext.GetRequiredService());
+
+ busFactoryConfigurator.UseConsumeFilter(typeof(ProcessTrackingConsumeFilter<>), busRegistrationContext);
+
+ busFactoryConfigurator.ReceiveEndpoint(UpdatePlayListPictureConsumer.QueueName, endpointConfigurator =>
+ {
+ endpointConfigurator.ConfigureConsumer(busRegistrationContext);
+ } );
+
+ busFactoryConfigurator.ReceiveEndpoint(RemoveFileConsumer.QueueName, endpointConfigurator =>
+ {
+ endpointConfigurator.ConfigureConsumer(busRegistrationContext);
+ } );
+
+ busFactoryConfigurator.ReceiveEndpoint(TranscodeAudioFromTrackConsumer.QueueName, endpointConfigurator =>
+ {
+ endpointConfigurator.ConfigureConsumer(busRegistrationContext);
+ } );
+
+ busFactoryConfigurator.ReceiveEndpoint(UpdateTrackPictureConsumer.QueueName, endpointConfigurator =>
+ {
+ endpointConfigurator.ConfigureConsumer(busRegistrationContext);
+ } );
+
+ ConfigureExecuteActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ RemoveFileFromBucketActivity.ExecuteEndpointName);
+
+ ConfigureExecuteActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ GenerateAudioWorkflowPathsActivity.ExecuteEndpointName);
+
+ ConfigureExecuteActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ GeneratePictureWorkflowPathsActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ ResizePictureActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ UpdatePlayListPictureActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ UpdateTrackPictureActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ DownloadFileFromBucketActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ TranscodeDashAudioActivity.ExecuteEndpointName);
+
+ ConfigureActivityEndpoint(
+ busFactoryConfigurator,
+ busRegistrationContext,
+ TransferFilesToBucket.ExecuteEndpointName);
+ } );
+ } );
+
+ return serviceDescriptors;
+ }
+
+ static void ConfigureRabbitMqHost(
+ IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
+ MessagingConfiguration massTransitConfiguration)
+ {
+ busFactoryConfigurator.Host(massTransitConfiguration.Host, options =>
+ {
+ options.Username(massTransitConfiguration.Username);
+ options.Password(massTransitConfiguration.Password);
+ } );
+ }
+
+ static void ConfigureExecuteActivityEndpoint(
+ IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
+ IBusRegistrationContext busRegistrationContext,
+ string endpointName)
+ where TActivity : class, IExecuteActivity
+ where TArguments : class
+ {
+ busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_execute", endpointConfigurator =>
+ {
+ endpointConfigurator.ExecuteActivityHost(busRegistrationContext);
+ } );
+ }
+
+ static void ConfigureActivityEndpoint(
+ IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
+ IBusRegistrationContext busRegistrationContext,
+ string endpointName)
+ where TActivity : class, IActivity
+ where TArguments : class
+ where TLog : class
+ {
+ busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_execute", endpointConfigurator =>
+ {
+ endpointConfigurator.ExecuteActivityHost(busRegistrationContext);
+ } );
+
+ busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_compensate", endpointConfigurator =>
+ {
+ endpointConfigurator.CompensateActivityHost(busRegistrationContext);
+ } );
+ }
+ }
+}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/MassTransitEventBus.cs b/Infrastructure/MassTransit/EventBus/MassTransitEventBus.cs
similarity index 100%
rename from Infrastructure/Messaging/MassTransitEventBus.cs
rename to Infrastructure/MassTransit/EventBus/MassTransitEventBus.cs
diff --git a/Infrastructure/Messaging/Filters/ProcessTrackingConsumeFilter.cs b/Infrastructure/MassTransit/Filters/ProcessTrackingConsumeFilter.cs
similarity index 100%
rename from Infrastructure/Messaging/Filters/ProcessTrackingConsumeFilter.cs
rename to Infrastructure/MassTransit/Filters/ProcessTrackingConsumeFilter.cs
diff --git a/Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs b/Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs
index b36e487..9b7ceb6 100644
--- a/Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs
+++ b/Infrastructure/MassTransit/Logs/UpdatePlayListPictureLog.cs
@@ -2,7 +2,8 @@ namespace Musify.Infrastructure.Messaging.Activities.Logs
{
public record UpdatePlayListPictureLog(
Guid PlayListId,
- string? PreviousSmallPictureKeyName,
- string? PreviousMediumPictureKeyName,
- string? PreviousLargePictureKeyName);
+ string PreviousOriginalPictureKeyName,
+ string PreviousSmallPictureKeyName,
+ string PreviousMediumPictureKeyName,
+ string PreviousLargePictureKeyName);
}
diff --git a/Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs b/Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs
index 0a9da77..bff1857 100644
--- a/Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs
+++ b/Infrastructure/MassTransit/Logs/UpdateTrackPictureLog.cs
@@ -2,7 +2,8 @@ namespace Musify.Infrastructure.Messaging.Activities.Logs
{
public record UpdateTrackPictureLog(
Guid TrackId,
- string? PreviousSmallPictureKeyName,
- string? PreviousMediumPictureKeyName,
- string? PreviousLargePictureKeyName);
+ string PreviousOriginalPictureKeyName,
+ string PreviousSmallPictureKeyName,
+ string PreviousMediumPictureKeyName,
+ string PreviousLargePictureKeyName);
}
diff --git a/Infrastructure/MassTransit/RoutingSlip/ActivityNames.cs b/Infrastructure/MassTransit/RoutingSlip/ActivityNames.cs
new file mode 100644
index 0000000..6f52fa2
--- /dev/null
+++ b/Infrastructure/MassTransit/RoutingSlip/ActivityNames.cs
@@ -0,0 +1,18 @@
+namespace Musify.Infrastructure.Messaging.RoutingSlip
+{
+ internal static class ActivityNames
+ {
+ internal const string GenerateAudioWorkflowPaths = "GenerateAudioWorkflowPaths";
+ internal const string GeneratePictureWorkflowPaths = "GeneratePictureWorkflowPaths";
+ internal const string DownloadFile = "DownloadFile";
+ internal const string TranscodeAudio = "TranscodeAudio";
+ internal const string ResizeSmall = "ResizeSmall";
+ internal const string ResizeMedium = "ResizeMedium";
+ internal const string ResizeLarge = "ResizeLarge";
+ internal const string UploadFiles = "UploadFiles";
+ internal const string RemoveFile = "RemoveFile";
+ internal const string DeleteOriginal = "DeleteOriginal";
+ internal const string UpdateTrackPicture = "UpdateTrackPicture";
+ internal const string UpdatePlayListPicture = "UpdatePlayListPicture";
+ }
+}
diff --git a/Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs b/Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs
new file mode 100644
index 0000000..262e2fc
--- /dev/null
+++ b/Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs
@@ -0,0 +1,50 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Configuration;
+using Musify.Infrastructure.Messaging.Activities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+
+namespace Musify.Infrastructure.Messaging.RoutingSlip.Builders
+{
+ public class AudioWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration)
+ {
+ public RoutingSlipBuilder Build(TranscodeAudioFromTrackEvent message, Guid? correlationId)
+ {
+ var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
+
+ routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.GenerateAudioWorkflowPaths,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(GenerateAudioWorkflowPathsActivity.ExecuteEndpointName),
+ new GenerateAudioWorkflowPathsArguments(
+ workerConfiguration.Routes.TemporaryFilesDirectory,
+ message.SourceKeyName));
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.DownloadFile,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
+ new DownloadFileFromBucketArguments(
+ message.SourceBucketName,
+ message.SourceKeyName,
+ RoutingSlipVariableNames.Audio.SourceFilePath));
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.TranscodeAudio,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(TranscodeDashAudioActivity.ExecuteEndpointName),
+ new TranscodeDashAudioArguments(
+ RoutingSlipVariableNames.Audio.SourceFilePath,
+ RoutingSlipVariableNames.Workflow.TempDirectory));
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.UploadFiles,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(TransferFilesToBucket.ExecuteEndpointName),
+ new TransferFilesToBucketArguments(
+ message.DestinationBucketName,
+ RoutingSlipVariableNames.Audio.TranscodedDirectory,
+ message.DestinationKeyName));
+
+ return routingSlipBuilder;
+ }
+ }
+}
diff --git a/Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs b/Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs
new file mode 100644
index 0000000..6abff35
--- /dev/null
+++ b/Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs
@@ -0,0 +1,159 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Configuration;
+using Musify.Infrastructure.Messaging.Activities;
+using Musify.Infrastructure.Messaging.Activities.Arguments;
+
+namespace Musify.Infrastructure.Messaging.RoutingSlip.Builders
+{
+ public class PictureWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration)
+ {
+ public RoutingSlipBuilder Build(UpdateTrackPictureEvent message, Guid? correlationId)
+ {
+ var routingSlipBuilder = BuildPictureWorkflow(
+ message.SourceBucketName,
+ message.SourceKeyName,
+ message.SmallPictureKeyName,
+ message.SmallPictureWidth,
+ message.SmallPictureHeight,
+ message.MediumPictureKeyName,
+ message.MediumPictureWidth,
+ message.MediumPictureHeight,
+ message.LargePictureKeyName,
+ message.LargePictureWidth,
+ message.LargePictureHeight,
+ correlationId);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.UpdateTrackPicture,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(UpdateTrackPictureActivity.ExecuteEndpointName),
+ new UpdateTrackPictureArguments(
+ message.TrackId,
+ message.SourceKeyName,
+ message.SmallPictureKeyName,
+ message.MediumPictureKeyName,
+ message.LargePictureKeyName));
+
+ return routingSlipBuilder;
+ }
+
+ public RoutingSlipBuilder Build(UpdatePlayListPictureEvent message, Guid? correlationId)
+ {
+ var routingSlipBuilder = BuildPictureWorkflow(
+ message.SourceBucketName,
+ message.SourceKeyName,
+ message.SmallPictureKeyName,
+ message.SmallPictureWidth,
+ message.SmallPictureHeight,
+ message.MediumPictureKeyName,
+ message.MediumPictureWidth,
+ message.MediumPictureHeight,
+ message.LargePictureKeyName,
+ message.LargePictureWidth,
+ message.LargePictureHeight,
+ correlationId);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.UpdatePlayListPicture,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(UpdatePlayListPictureActivity.ExecuteEndpointName),
+ new UpdatePlayListPictureArguments(
+ message.PlayListId,
+ message.SourceKeyName,
+ message.SmallPictureKeyName,
+ message.MediumPictureKeyName,
+ message.LargePictureKeyName));
+
+ return routingSlipBuilder;
+ }
+
+ RoutingSlipBuilder BuildPictureWorkflow(
+ string sourceBucketName,
+ string sourceKeyName,
+ string smallPictureKeyName,
+ int smallPictureWidth,
+ int smallPictureHeight,
+ string mediumPictureKeyName,
+ int mediumPictureWidth,
+ int mediumPictureHeight,
+ string largePictureKeyName,
+ int largePictureWidth,
+ int largePictureHeight,
+ Guid? correlationId)
+ {
+ var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
+
+ routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.GeneratePictureWorkflowPaths,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(GeneratePictureWorkflowPathsActivity.ExecuteEndpointName),
+ new GeneratePictureWorkflowPathsArguments(
+ workerConfiguration.Routes.TemporaryFilesDirectory,
+ sourceKeyName,
+ smallPictureKeyName,
+ mediumPictureKeyName,
+ largePictureKeyName));
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.DownloadFile,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
+ new DownloadFileFromBucketArguments(
+ sourceBucketName,
+ sourceKeyName,
+ RoutingSlipVariableNames.Picture.OriginalFilePath));
+
+ AddResizeActivity(
+ routingSlipBuilder,
+ ActivityNames.ResizeSmall,
+ RoutingSlipVariableNames.Picture.SmallResizedFilePath,
+ smallPictureWidth,
+ smallPictureHeight);
+
+ AddResizeActivity(
+ routingSlipBuilder,
+ ActivityNames.ResizeMedium,
+ RoutingSlipVariableNames.Picture.MediumResizedFilePath,
+ mediumPictureWidth,
+ mediumPictureHeight);
+
+ AddResizeActivity(
+ routingSlipBuilder,
+ ActivityNames.ResizeLarge,
+ RoutingSlipVariableNames.Picture.LargeResizedFilePath,
+ largePictureWidth,
+ largePictureHeight);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.DeleteOriginal,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
+ new RemoveFileFromBucketArguments(sourceBucketName, sourceKeyName));
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.UploadFiles,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(TransferFilesToBucket.ExecuteEndpointName),
+ new TransferFilesToBucketArguments(
+ sourceBucketName,
+ RoutingSlipVariableNames.Workflow.TempDirectory,
+ DestinationKeyNameVariableName: RoutingSlipVariableNames.Picture.DestinationFolderName));
+
+ return routingSlipBuilder;
+ }
+
+ static void AddResizeActivity(
+ RoutingSlipBuilder routingSlipBuilder,
+ string activityName,
+ string destinationFilePathVariableName,
+ int width,
+ int height)
+ {
+ routingSlipBuilder.AddActivity(
+ activityName,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(ResizePictureActivity.ExecuteEndpointName),
+ new ResizePictureLocalArguments(
+ RoutingSlipVariableNames.Picture.OriginalFilePath,
+ destinationFilePathVariableName,
+ width,
+ height));
+ }
+ }
+}
diff --git a/Infrastructure/MassTransit/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs b/Infrastructure/MassTransit/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs
new file mode 100644
index 0000000..3728465
--- /dev/null
+++ b/Infrastructure/MassTransit/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs
@@ -0,0 +1,25 @@
+using MassTransit;
+using Musify.Application.Events;
+using Musify.Infrastructure.Messaging.Activities;
+
+namespace Musify.Infrastructure.Messaging.RoutingSlip.Builders
+{
+ public class RemoveFileRoutingSlipBuilder
+ {
+ public RoutingSlipBuilder Build(RemoveFileEvent message, Guid? correlationId)
+ {
+ var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
+
+ routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
+
+ routingSlipBuilder.AddActivity(
+ ActivityNames.RemoveFile,
+ Consumers.MessagingHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
+ new Activities.Arguments.RemoveFileFromBucketArguments(
+ message.BucketName,
+ message.KeyName));
+
+ return routingSlipBuilder;
+ }
+ }
+}
diff --git a/Infrastructure/MassTransit/RoutingSlip/Variables/RoutingSlipVariableNames.cs b/Infrastructure/MassTransit/RoutingSlip/Variables/RoutingSlipVariableNames.cs
new file mode 100644
index 0000000..3b9bbb0
--- /dev/null
+++ b/Infrastructure/MassTransit/RoutingSlip/Variables/RoutingSlipVariableNames.cs
@@ -0,0 +1,27 @@
+namespace Musify.Infrastructure.Messaging
+{
+ internal static class RoutingSlipVariableNames
+ {
+ internal static class Workflow
+ {
+ internal const string TempDirectory = "Workflow.TempDirectory";
+ internal const string CorrelationId = "Workflow.CorrelationId";
+ }
+
+ internal static class Audio
+ {
+ internal const string SourceFilePath = "Audio.SourceFilePath";
+ internal const string WorkingDirectory = "Audio.WorkingDirectory";
+ internal const string TranscodedDirectory = "Audio.TranscodedDirectory";
+ }
+
+ internal static class Picture
+ {
+ internal const string OriginalFilePath = "Picture.OriginalFilePath";
+ internal const string SmallResizedFilePath = "Picture.SmallResizedFilePath";
+ internal const string MediumResizedFilePath = "Picture.MediumResizedFilePath";
+ internal const string LargeResizedFilePath = "Picture.LargeResizedFilePath";
+ internal const string DestinationFolderName = "Picture.DestinationFolderName";
+ }
+ }
+}
diff --git a/Infrastructure/Messaging/Activities/Arguments/CopyFileArguments.cs b/Infrastructure/Messaging/Activities/Arguments/CopyFileArguments.cs
deleted file mode 100644
index b5bbc96..0000000
--- a/Infrastructure/Messaging/Activities/Arguments/CopyFileArguments.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Musify.Infrastructure.Messaging.Activities.Arguments
-{
- public record CopyFileArguments(
- string SourceBucketName,
- string SourceKeyName,
- string DestinationBucketName,
- string DestinationKeyName);
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Arguments/ResizePictureArgument.cs b/Infrastructure/Messaging/Activities/Arguments/ResizePictureArgument.cs
deleted file mode 100644
index 8d54d7e..0000000
--- a/Infrastructure/Messaging/Activities/Arguments/ResizePictureArgument.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Musify.Infrastructure.Messaging.Activities.Arguments
-{
- public record ResizePictureArgument(
- string OriginalBucketName,
- string OriginalKeyName,
- string DestinationBucketName,
- string DestinationKeyName,
- int Width,
- int Height);
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Arguments/TranscodeDashAudioArguments.cs b/Infrastructure/Messaging/Activities/Arguments/TranscodeDashAudioArguments.cs
deleted file mode 100644
index a983378..0000000
--- a/Infrastructure/Messaging/Activities/Arguments/TranscodeDashAudioArguments.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Musify.Infrastructure.Messaging.Activities.Arguments
-{
- public record TranscodeDashAudioArguments();
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/DownloadFileFromBucketActivity.cs b/Infrastructure/Messaging/Activities/DownloadFileFromBucketActivity.cs
index 5cf5e45..e69de29 100644
--- a/Infrastructure/Messaging/Activities/DownloadFileFromBucketActivity.cs
+++ b/Infrastructure/Messaging/Activities/DownloadFileFromBucketActivity.cs
@@ -1,80 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class DownloadFileFromBucketActivity(
- IStorageHandler storageHandler,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Download-File-From-Bucket";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(DownloadFileFromBucketActivity),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- try
- {
- var getFile = await storageHandler.GetFileAsync(
- executeContext.Arguments.BucketName,
- executeContext.Arguments.KeyName,
- executeContext.CancellationToken);
-
- if (!getFile.IsSuccess)
- {
- var errorMessage = string.Join("; ", getFile.Errors);
- logger.LogWarning("Failed to download file from bucket. Bucket: {BucketName}, Key: {KeyName}, Errors: {Errors}",
- executeContext.Arguments.BucketName,
- executeContext.Arguments.KeyName,
- errorMessage);
- throw new Exception(errorMessage);
- }
-
- using var fileStream = getFile.Value;
- if (fileStream.CanSeek)
- fileStream.Position = 0;
-
- var fileName = Path.GetFileName(executeContext.Arguments.KeyName);
- var folderName = Guid.NewGuid().ToString();
- var workingDirectory = Path.Combine(executeContext.Arguments.DestinationPath, folderName);
- var destinationPath = Path.Combine(workingDirectory, fileName);
-
- Directory.CreateDirectory(workingDirectory);
-
- using var destinationStream = File.Create(destinationPath);
- await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken);
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.CompletedWithVariables(new
- {
- SourceFilePath = destinationPath,
- WorkingDirectory = workingDirectory,
- DestinationFolderName = folderName
- } );
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error downloading file from bucket");
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/GenerateAudioWorkflowPathsActivity.cs b/Infrastructure/Messaging/Activities/GenerateAudioWorkflowPathsActivity.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/Activities/GeneratePictureWorkflowPathsActivity.cs b/Infrastructure/Messaging/Activities/GeneratePictureWorkflowPathsActivity.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/Activities/Logs/DownloadFileFromBucketLog.cs b/Infrastructure/Messaging/Activities/Logs/DownloadFileFromBucketLog.cs
index 5c60a7e..e69de29 100644
--- a/Infrastructure/Messaging/Activities/Logs/DownloadFileFromBucketLog.cs
+++ b/Infrastructure/Messaging/Activities/Logs/DownloadFileFromBucketLog.cs
@@ -1,7 +0,0 @@
-namespace Musify.Infrastructure.Messaging.Activities.Logs
-{
- public record DownloadFileFromBucketLog(
- string BucketName,
- string KeyName,
- string DestinationPath);
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/Logs/TranscodeDashAudioLog.cs b/Infrastructure/Messaging/Activities/Logs/TranscodeDashAudioLog.cs
deleted file mode 100644
index 2345392..0000000
--- a/Infrastructure/Messaging/Activities/Logs/TranscodeDashAudioLog.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Musify.Infrastructure.Messaging.Activities.Logs
-{
- public record TranscodeDashAudioLog(string WorkingDirectory);
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/ResizePictureActivity.cs b/Infrastructure/Messaging/Activities/ResizePictureActivity.cs
index 6ea330f..e69de29 100644
--- a/Infrastructure/Messaging/Activities/ResizePictureActivity.cs
+++ b/Infrastructure/Messaging/Activities/ResizePictureActivity.cs
@@ -1,87 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class ResizePictureActivity(
- IPictureHandler pictureHandler,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Resize-Picture";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(ResizePictureActivity),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- try
- {
- if (!File.Exists(executeContext.Arguments.SourceFilePath))
- {
- logger.LogWarning("Source file not found at {SourceFilePath}", executeContext.Arguments.SourceFilePath);
- throw new FileNotFoundException($"Source file not found: {executeContext.Arguments.SourceFilePath}");
- }
-
- await using var fileStream = File.OpenRead(executeContext.Arguments.SourceFilePath);
-
- var resizedPicture = await pictureHandler.ResizePictureAsync(
- fileStream,
- executeContext.Arguments.Width,
- executeContext.Arguments.Height,
- executeContext.CancellationToken);
-
- if (!resizedPicture.IsSuccess)
- {
- var errorMessage = string.Join("; ", resizedPicture.Errors);
- logger.LogError("Failed to resize picture {SourceFilePath} to {Width}x{Height}. Errors: {Errors}",
- executeContext.Arguments.SourceFilePath,
- executeContext.Arguments.Width,
- executeContext.Arguments.Height,
- errorMessage);
- throw new Exception(errorMessage);
- }
-
- var destinationDirectory = Path.GetDirectoryName(executeContext.Arguments.DestinationFilePath);
- if (!string.IsNullOrEmpty(destinationDirectory))
- {
- Directory.CreateDirectory(destinationDirectory);
- }
-
- await using var destinationStream = File.Create(executeContext.Arguments.DestinationFilePath);
- await resizedPicture.Value.CopyToAsync(destinationStream, executeContext.CancellationToken);
-
- logger.LogDebug("Picture resized from {SourceFilePath} to {DestinationFilePath} ({Width}x{Height})",
- executeContext.Arguments.SourceFilePath,
- executeContext.Arguments.DestinationFilePath,
- executeContext.Arguments.Width,
- executeContext.Arguments.Height);
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.Completed();
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error resizing picture {SourceFilePath}",
- executeContext.Arguments.SourceFilePath);
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/TranscodeDashAudioActivity.cs b/Infrastructure/Messaging/Activities/TranscodeDashAudioActivity.cs
index 8094999..e69de29 100644
--- a/Infrastructure/Messaging/Activities/TranscodeDashAudioActivity.cs
+++ b/Infrastructure/Messaging/Activities/TranscodeDashAudioActivity.cs
@@ -1,70 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class TranscodeDashAudioActivity(
- IAudioTranscoder audioTranscoder,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Transcode-Dash-Audio";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(TranscodeDashAudioActivity),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- var sourceFilePath = executeContext.GetVariable("SourceFilePath");
- var workingDirectory = executeContext.GetVariable("WorkingDirectory");
-
- if (string.IsNullOrWhiteSpace(sourceFilePath) || string.IsNullOrWhiteSpace(workingDirectory))
- {
- throw new InvalidOperationException("Transcode activity requires SourceFilePath and WorkingDirectory variables.");
- }
-
- try
- {
- await using var fileStream = File.OpenRead(sourceFilePath);
-
- var result = await audioTranscoder.TranscodeToDashAsync(
- fileStream,
- workingDirectory,
- executeContext.CancellationToken);
-
- if (!result.IsSuccess)
- {
- var errorMessage = string.Join("; ", result.Errors);
- logger.LogError("Transcoding failed for file {SourceFilePath}. Errors: {ErrorMessage}",
- sourceFilePath, errorMessage);
- throw new Exception(errorMessage);
- }
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.Completed();
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error during transcoding activity for file {SourceFilePath}",
- sourceFilePath);
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/TransferFilesToBucket.cs b/Infrastructure/Messaging/Activities/TransferFilesToBucket.cs
index 13ca1e4..e69de29 100644
--- a/Infrastructure/Messaging/Activities/TransferFilesToBucket.cs
+++ b/Infrastructure/Messaging/Activities/TransferFilesToBucket.cs
@@ -1,71 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class TransferFilesToBucket(
- IStorageHandler storageHandler,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Transfer-Files-To-Bucket";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(TransferFilesToBucket),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- var folderPath = executeContext.GetVariable("WorkingDirectory");
- if (string.IsNullOrWhiteSpace(folderPath))
- {
- throw new InvalidOperationException("Transfer activity requires WorkingDirectory variable.");
- }
-
- try
- {
- var transferFilesResult = await storageHandler.TransferFilesAsync(
- folderPath,
- executeContext.Arguments.DestinationBucketName,
- executeContext.Arguments.DestinationKeyName,
- executeContext.CancellationToken);
-
- if (!transferFilesResult.IsSuccess)
- {
- var errorMessage = string.Join("; ", transferFilesResult.Errors);
- logger.LogWarning("Failed to transfer files from {FolderPath} to bucket {DestinationBucketName}, key {DestinationKeyName}. Errors: {Errors}",
- folderPath,
- executeContext.Arguments.DestinationBucketName,
- executeContext.Arguments.DestinationKeyName,
- errorMessage);
- throw new Exception(errorMessage);
- }
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.Completed();
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error transferring files from {FolderPath} to bucket {DestinationBucketName}",
- folderPath,
- executeContext.Arguments.DestinationBucketName);
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/UpdatePlayListPictureActivity.cs b/Infrastructure/Messaging/Activities/UpdatePlayListPictureActivity.cs
index b369773..e69de29 100644
--- a/Infrastructure/Messaging/Activities/UpdatePlayListPictureActivity.cs
+++ b/Infrastructure/Messaging/Activities/UpdatePlayListPictureActivity.cs
@@ -1,68 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class UpdatePlayListPictureActivity(
- IDatabase database,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Update-PlayList-Picture";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(UpdatePlayListPictureActivity),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- try
- {
- var playList = await database.PlayLists.FindAsync(
- [executeContext.Arguments.PlayListId],
- cancellationToken: executeContext.CancellationToken);
-
- if (playList is null)
- {
- logger.LogWarning("PlayList with id {PlayListId} not found",
- executeContext.Arguments.PlayListId);
- throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found");
- }
-
- playList.SmallPictureKeyName = executeContext.Arguments.SmallPictureKeyName;
- playList.MediumPictureKeyName = executeContext.Arguments.MediumPictureKeyName;
- playList.LargePictureKeyName = executeContext.Arguments.LargePictureKeyName;
-
- database.PlayLists.Update(playList);
- await database.SaveChangesAsync(executeContext.CancellationToken);
-
- logger.LogInformation("Updated PlayList {PlayListId} pictures",
- executeContext.Arguments.PlayListId);
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.Completed();
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error updating PlayList {PlayListId} pictures",
- executeContext.Arguments.PlayListId);
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs b/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs
index 9b7291d..e69de29 100644
--- a/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs
+++ b/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs
@@ -1,68 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.Logging;
-using Musify.Application.Contracts.Infrastructure;
-using Musify.Domain.Entities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Activities
-{
- public class UpdateTrackPictureActivity(
- IDatabase database,
- ILogger logger,
- IProcessTrackingStore processTrackingStore)
- : IExecuteActivity
- {
- public const string ExecuteEndpointName = "Update-Track-Picture";
-
- public async Task Execute(ExecuteContext executeContext)
- {
- var processId = await processTrackingStore.GetOrCreateProcessAsync(
- "RoutingSlip",
- executeContext.CorrelationId ?? executeContext.TrackingNumber,
- executeContext.ConversationId,
- executeContext.MessageId,
- executeContext.CancellationToken);
-
- var stepId = await processTrackingStore.StartStepAsync(
- processId,
- nameof(UpdateTrackPictureActivity),
- ProcessStepComponentType.Activity,
- 0,
- executeContext.CancellationToken);
-
- try
- {
- var track = await database.Tracks.FindAsync(
- [executeContext.Arguments.TrackId],
- cancellationToken: executeContext.CancellationToken);
-
- if (track is null)
- {
- logger.LogWarning("Track with id {TrackId} not found",
- executeContext.Arguments.TrackId);
- throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
- }
-
- track.SmallPictureKeyName = executeContext.Arguments.SmallPictureKeyName;
- track.MediumPictureKeyName = executeContext.Arguments.MediumPictureKeyName;
- track.LargePictureKeyName = executeContext.Arguments.LargePictureKeyName;
-
- database.Tracks.Update(track);
- await database.SaveChangesAsync(executeContext.CancellationToken);
-
- logger.LogInformation("Updated Track {TrackId} pictures",
- executeContext.Arguments.TrackId);
-
- await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
- return executeContext.Completed();
- }
- catch (Exception exception)
- {
- logger.LogError(exception, "Error updating Track {TrackId} pictures",
- executeContext.Arguments.TrackId);
- await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken);
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Consumers/RemoveFileConsumer.cs b/Infrastructure/Messaging/Consumers/RemoveFileConsumer.cs
index 8a78a1a..e69de29 100644
--- a/Infrastructure/Messaging/Consumers/RemoveFileConsumer.cs
+++ b/Infrastructure/Messaging/Consumers/RemoveFileConsumer.cs
@@ -1,28 +0,0 @@
-using MassTransit;
-using Musify.Application.Events;
-using Musify.Infrastructure.Messaging.Activities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Consumers
-{
- public class RemoveFileConsumer(IBus bus)
- : IConsumer
- {
- public const string QueueName = "Remove-File";
-
- public async Task Consume(ConsumeContext consumeContext)
- {
- var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
-
- routingSlipBuilder.AddActivity(
- "RemoveFile",
- MessagingHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
- new RemoveFileFromBucketArguments(
- consumeContext.Message.BucketName,
- consumeContext.Message.KeyName));
-
- var routingSlip = routingSlipBuilder.Build();
- await bus.Execute(routingSlip);
- }
- }
-}
diff --git a/Infrastructure/Messaging/Consumers/TranscodeAudioFromTrackConsumer.cs b/Infrastructure/Messaging/Consumers/TranscodeAudioFromTrackConsumer.cs
index faf5853..e69de29 100644
--- a/Infrastructure/Messaging/Consumers/TranscodeAudioFromTrackConsumer.cs
+++ b/Infrastructure/Messaging/Consumers/TranscodeAudioFromTrackConsumer.cs
@@ -1,44 +0,0 @@
-using MassTransit;
-using Musify.Application.Events;
-using Musify.Infrastructure.Configuration;
-using Musify.Infrastructure.Messaging.Activities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Consumers
-{
- public class TranscodeAudioFromTrackConsumer(
- IBus bus,
- WorkerConfiguration workerConfiguration)
- : IConsumer
- {
- public const string QueueName = "Transcode-Audio-From-Track";
-
- public async Task Consume(ConsumeContext consumeContext)
- {
- var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
-
- routingSlipBuilder.AddActivity(
- "DownloadFile",
- MessagingHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
- new DownloadFileFromBucketArguments(
- consumeContext.Message.SourceBucketName,
- consumeContext.Message.SourceKeyName,
- workerConfiguration.Routes.TemporaryFilesDirectory));
-
- routingSlipBuilder.AddActivity(
- "TranscodeAudio",
- MessagingHelper.BuildExecuteActivityUri(TranscodeDashAudioActivity.ExecuteEndpointName),
- new TranscodeDashAudioArguments());
-
- routingSlipBuilder.AddActivity(
- "UploadFile",
- MessagingHelper.BuildExecuteActivityUri(TransferFilesToBucket.ExecuteEndpointName),
- new TransferFilesToBucketArguments(
- consumeContext.Message.DestinationBucketName,
- consumeContext.Message.DestinationKeyName));
-
- var routingSlip = routingSlipBuilder.Build();
- await bus.Execute(routingSlip);
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs b/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs
index 0ba7a98..e69de29 100644
--- a/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs
+++ b/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs
@@ -1,112 +0,0 @@
-using MassTransit;
-using Musify.Application.Events;
-using Musify.Infrastructure.Configuration;
-using Musify.Infrastructure.Messaging.Activities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Consumers
-{
- public class UpdatePlayListPictureConsumer(
- IBus bus,
- WorkerConfiguration workerConfiguration)
- : IConsumer
- {
- public const string QueueName = "Update-PlayList-Picture";
-
- public async Task Consume(ConsumeContext consumeContext)
- {
- var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
- var folderName = Guid.NewGuid().ToString();
- var workingDirectory = Path.Combine(workerConfiguration.Routes.TemporaryFilesDirectory, folderName);
-
- routingSlipBuilder.AddActivity(
- "DownloadFile",
- MessagingHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
- new DownloadFileFromBucketArguments(
- consumeContext.Message.SourceBucketName,
- consumeContext.Message.SourceKeyName,
- workingDirectory));
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeSmall",
- consumeContext.Message.SmallPictureKeyName,
- consumeContext.Message.SmallPictureWidth,
- consumeContext.Message.SmallPictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeMedium",
- consumeContext.Message.MediumPictureKeyName,
- consumeContext.Message.MediumPictureWidth,
- consumeContext.Message.MediumPictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeLarge",
- consumeContext.Message.LargePictureKeyName,
- consumeContext.Message.LargePictureWidth,
- consumeContext.Message.LargePictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- routingSlipBuilder.AddActivity(
- "DeleteOriginal",
- MessagingHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
- new RemoveFileFromBucketArguments(
- consumeContext.Message.SourceBucketName,
- consumeContext.Message.SourceKeyName));
-
- routingSlipBuilder.AddActivity(
- "UploadFiles",
- MessagingHelper.BuildExecuteActivityUri(TransferFilesToBucket.ExecuteEndpointName),
- new TransferFilesToBucketArguments(
- consumeContext.Message.SourceBucketName,
- folderName));
-
- routingSlipBuilder.AddActivity(
- "UpdatePlayListPicture",
- MessagingHelper.BuildExecuteActivityUri(UpdatePlayListPictureActivity.ExecuteEndpointName),
- new UpdatePlayListPictureArguments(
- consumeContext.Message.PlayListId,
- consumeContext.Message.SmallPictureKeyName,
- consumeContext.Message.MediumPictureKeyName,
- consumeContext.Message.LargePictureKeyName));
-
- var routingSlip = routingSlipBuilder.Build();
- await bus.Execute(routingSlip);
- }
-
- void AddResizeActivity(
- RoutingSlipBuilder routingSlipBuilder,
- string activityName,
- string destinationKeyName,
- int width,
- int height,
- string originalPictureKeyName,
- string bucketName,
- string workingDirectory)
- {
- var fileName = Path.GetFileName(originalPictureKeyName);
- var sourceFilePath = Path.Combine(workingDirectory, fileName);
- var destinationFileName = Path.GetFileNameWithoutExtension(destinationKeyName) + Path.GetExtension(fileName);
- var destinationFilePath = Path.Combine(workingDirectory, destinationFileName);
-
- routingSlipBuilder.AddActivity(
- activityName,
- MessagingHelper.BuildExecuteActivityUri(ResizePictureActivity.ExecuteEndpointName),
- new ResizePictureLocalArguments(
- sourceFilePath,
- destinationFilePath,
- width,
- height));
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs b/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs
index f019285..e69de29 100644
--- a/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs
+++ b/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs
@@ -1,112 +0,0 @@
-using MassTransit;
-using Musify.Application.Events;
-using Musify.Infrastructure.Configuration;
-using Musify.Infrastructure.Messaging.Activities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-
-namespace Musify.Infrastructure.Messaging.Consumers
-{
- public class UpdateTrackPictureConsumer(
- IBus bus,
- WorkerConfiguration workerConfiguration)
- : IConsumer
- {
- public const string QueueName = "Update-Track-Picture";
-
- public async Task Consume(ConsumeContext consumeContext)
- {
- var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
- var folderName = Guid.NewGuid().ToString();
- var workingDirectory = Path.Combine(workerConfiguration.Routes.TemporaryFilesDirectory, folderName);
-
- routingSlipBuilder.AddActivity(
- "DownloadFile",
- MessagingHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
- new DownloadFileFromBucketArguments(
- consumeContext.Message.SourceBucketName,
- consumeContext.Message.SourceKeyName,
- workingDirectory));
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeSmall",
- consumeContext.Message.SmallPictureKeyName,
- consumeContext.Message.SmallPictureWidth,
- consumeContext.Message.SmallPictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeMedium",
- consumeContext.Message.MediumPictureKeyName,
- consumeContext.Message.MediumPictureWidth,
- consumeContext.Message.MediumPictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- AddResizeActivity(
- routingSlipBuilder,
- "ResizeLarge",
- consumeContext.Message.LargePictureKeyName,
- consumeContext.Message.LargePictureWidth,
- consumeContext.Message.LargePictureHeight,
- consumeContext.Message.SourceKeyName,
- consumeContext.Message.SourceBucketName,
- workingDirectory);
-
- routingSlipBuilder.AddActivity(
- "DeleteOriginal",
- MessagingHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
- new RemoveFileFromBucketArguments(
- consumeContext.Message.SourceBucketName,
- consumeContext.Message.SourceKeyName));
-
- routingSlipBuilder.AddActivity(
- "UploadFiles",
- MessagingHelper.BuildExecuteActivityUri(TransferFilesToBucket.ExecuteEndpointName),
- new TransferFilesToBucketArguments(
- consumeContext.Message.SourceBucketName,
- folderName));
-
- routingSlipBuilder.AddActivity(
- "UpdateTrackPicture",
- MessagingHelper.BuildExecuteActivityUri(UpdateTrackPictureActivity.ExecuteEndpointName),
- new UpdateTrackPictureArguments(
- consumeContext.Message.TrackId,
- consumeContext.Message.SmallPictureKeyName,
- consumeContext.Message.MediumPictureKeyName,
- consumeContext.Message.LargePictureKeyName));
-
- var routingSlip = routingSlipBuilder.Build();
- await bus.Execute(routingSlip);
- }
-
- void AddResizeActivity(
- RoutingSlipBuilder routingSlipBuilder,
- string activityName,
- string destinationKeyName,
- int width,
- int height,
- string originalPictureKeyName,
- string bucketName,
- string workingDirectory)
- {
- var fileName = Path.GetFileName(originalPictureKeyName);
- var sourceFilePath = Path.Combine(workingDirectory, fileName);
- var destinationFileName = Path.GetFileNameWithoutExtension(destinationKeyName) + Path.GetExtension(fileName);
- var destinationFilePath = Path.Combine(workingDirectory, destinationFileName);
-
- routingSlipBuilder.AddActivity(
- activityName,
- MessagingHelper.BuildExecuteActivityUri(ResizePictureActivity.ExecuteEndpointName),
- new ResizePictureLocalArguments(
- sourceFilePath,
- destinationFilePath,
- width,
- height));
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/MessagingDependencyInjection.cs b/Infrastructure/Messaging/MessagingDependencyInjection.cs
index 59e9c11..e69de29 100644
--- a/Infrastructure/Messaging/MessagingDependencyInjection.cs
+++ b/Infrastructure/Messaging/MessagingDependencyInjection.cs
@@ -1,139 +0,0 @@
-using MassTransit;
-using Microsoft.Extensions.DependencyInjection;
-using Musify.Infrastructure.Configuration;
-using Musify.Infrastructure.Messaging.Activities;
-using Musify.Infrastructure.Messaging.Activities.Arguments;
-using Musify.Infrastructure.Messaging.Consumers;
-using Musify.Infrastructure.Messaging.Filters;
-
-namespace Musify.Infrastructure.Messaging
-{
- public static class MessagingDependencyInjection
- {
- public static IServiceCollection AddMassTransitClient(this IServiceCollection serviceDescriptors)
- {
- serviceDescriptors.AddMassTransit(options =>
- {
- options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
- ConfigureRabbitMqHost(
- busFactoryConfigurator,
- busRegistrationContext.GetRequiredService()));
- } );
-
- return serviceDescriptors;
- }
-
- public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors)
- {
- serviceDescriptors.AddMassTransit(options =>
- {
- // Register consumers
- options.AddConsumer();
- options.AddConsumer();
- options.AddConsumer();
- options.AddConsumer();
-
- // Register execute activities
- options.AddExecuteActivity();
- options.AddExecuteActivity();
- options.AddExecuteActivity();
- options.AddExecuteActivity();
- options.AddExecuteActivity();
- options.AddExecuteActivity();
- options.AddExecuteActivity();
-
- options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
- {
- ConfigureRabbitMqHost(
- busFactoryConfigurator,
- busRegistrationContext.GetRequiredService());
-
- busFactoryConfigurator.UseConsumeFilter(typeof(ProcessTrackingConsumeFilter<>), busRegistrationContext);
-
- // Consumer endpoints
- busFactoryConfigurator.ReceiveEndpoint(UpdatePlayListPictureConsumer.QueueName, endpointConfigurator =>
- {
- endpointConfigurator.ConfigureConsumer(busRegistrationContext);
- } );
-
- busFactoryConfigurator.ReceiveEndpoint(RemoveFileConsumer.QueueName, endpointConfigurator =>
- {
- endpointConfigurator.ConfigureConsumer(busRegistrationContext);
- } );
-
- busFactoryConfigurator.ReceiveEndpoint(TranscodeAudioFromTrackConsumer.QueueName, endpointConfigurator =>
- {
- endpointConfigurator.ConfigureConsumer(busRegistrationContext);
- } );
-
- busFactoryConfigurator.ReceiveEndpoint(UpdateTrackPictureConsumer.QueueName, endpointConfigurator =>
- {
- endpointConfigurator.ConfigureConsumer(busRegistrationContext);
- } );
-
- // Activity endpoints
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- ResizePictureActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- RemoveFileFromBucketActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- UpdatePlayListPictureActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- UpdateTrackPictureActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- DownloadFileFromBucketActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- TranscodeDashAudioActivity.ExecuteEndpointName);
-
- ConfigureExecuteActivityEndpoint(
- busFactoryConfigurator,
- busRegistrationContext,
- TransferFilesToBucket.ExecuteEndpointName);
- } );
- } );
-
- return serviceDescriptors;
- }
-
- static void ConfigureRabbitMqHost(
- IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
- MessagingConfiguration massTransitConfiguration)
- {
- busFactoryConfigurator.Host(massTransitConfiguration.Host, options =>
- {
- options.Username(massTransitConfiguration.Username);
- options.Password(massTransitConfiguration.Password);
- } );
- }
-
- static void ConfigureExecuteActivityEndpoint(
- IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
- IBusRegistrationContext busRegistrationContext,
- string endpointName)
- where TActivity : class, IExecuteActivity
- where TArguments : class
- {
- busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_execute", endpointConfigurator =>
- {
- endpointConfigurator.ExecuteActivityHost(busRegistrationContext);
- } );
- }
- }
-}
\ No newline at end of file
diff --git a/Infrastructure/Messaging/RoutingSlip/ActivityNames.cs b/Infrastructure/Messaging/RoutingSlip/ActivityNames.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs b/Infrastructure/Messaging/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs b/Infrastructure/Messaging/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs b/Infrastructure/Messaging/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Messaging/RoutingSlipVariableNames.cs b/Infrastructure/Messaging/RoutingSlipVariableNames.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.Designer.cs b/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.Designer.cs
new file mode 100644
index 0000000..34c7024
--- /dev/null
+++ b/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.Designer.cs
@@ -0,0 +1,392 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Musify.Infrastructure.Persistence;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Musify.Infrastructure.Persistence.Migrations
+{
+ [DbContext(typeof(Database))]
+ [Migration("20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList")]
+ partial class AddOriginalPictureKeyNameToTrackAndPlayList
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.5")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasMaxLength(254)
+ .HasColumnType("character varying(254)");
+
+ b.Property("LargePictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("MediumPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("NormalizedName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("OriginalPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("SmallPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UpdatedDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("PlayLists");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AddedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("PlayListId")
+ .HasColumnType("uuid");
+
+ b.Property("Position")
+ .HasColumnType("integer");
+
+ b.Property("TrackId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PlayListId");
+
+ b.HasIndex("TrackId");
+
+ b.ToTable("PlayListHasTrack");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.ProcessExecution", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConversationId")
+ .HasColumnType("uuid");
+
+ b.Property("CorrelationId")
+ .HasColumnType("uuid");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b.Property("FinishedDateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MessageId")
+ .HasColumnType("uuid");
+
+ b.Property("ProcessName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("StartedDateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CorrelationId", "ProcessName");
+
+ b.ToTable("ProcessExecution");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.ProcessStepExecution", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempt")
+ .HasColumnType("integer");
+
+ b.Property("ComponentType")
+ .HasColumnType("integer");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(2048)
+ .HasColumnType("character varying(2048)");
+
+ b.Property("FinishedDateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ProcessExecutionId")
+ .HasColumnType("uuid");
+
+ b.Property("StartedDateTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("StepName")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ProcessExecutionId", "StepName");
+
+ b.ToTable("ProcessStepExecution");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LargePictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("MediumPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("NormalizedTitle")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("OriginalPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("SmallPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("UpdatedDate")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("Tracks");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.Upload", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("BucketName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ContentType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("KeyName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("State")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Upload");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("User");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("TrackId")
+ .HasColumnType("uuid");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TrackId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("UserHasTrack");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
+ {
+ b.HasOne("Musify.Domain.Entities.User", "User")
+ .WithMany("PlayLists")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
+ {
+ b.HasOne("Musify.Domain.Entities.PlayList", "PlayList")
+ .WithMany("PlayListTracks")
+ .HasForeignKey("PlayListId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Musify.Domain.Entities.Track", "Track")
+ .WithMany("PlayListTracks")
+ .HasForeignKey("TrackId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("PlayList");
+
+ b.Navigation("Track");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.ProcessStepExecution", b =>
+ {
+ b.HasOne("Musify.Domain.Entities.ProcessExecution", "ProcessExecution")
+ .WithMany("Steps")
+ .HasForeignKey("ProcessExecutionId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ProcessExecution");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b =>
+ {
+ b.HasOne("Musify.Domain.Entities.Track", "Track")
+ .WithMany("UserTracks")
+ .HasForeignKey("TrackId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Musify.Domain.Entities.User", "User")
+ .WithMany("UserTracks")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Track");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
+ {
+ b.Navigation("PlayListTracks");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.ProcessExecution", b =>
+ {
+ b.Navigation("Steps");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
+ {
+ b.Navigation("PlayListTracks");
+
+ b.Navigation("UserTracks");
+ });
+
+ modelBuilder.Entity("Musify.Domain.Entities.User", b =>
+ {
+ b.Navigation("PlayLists");
+
+ b.Navigation("UserTracks");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.cs b/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.cs
new file mode 100644
index 0000000..983bcd9
--- /dev/null
+++ b/Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.cs
@@ -0,0 +1,42 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Musify.Infrastructure.Persistence.Migrations
+{
+ ///
+ public partial class AddOriginalPictureKeyNameToTrackAndPlayList : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "OriginalPictureKeyName",
+ table: "Tracks",
+ type: "character varying(64)",
+ maxLength: 64,
+ nullable: false,
+ defaultValue: "");
+
+ migrationBuilder.AddColumn(
+ name: "OriginalPictureKeyName",
+ table: "PlayLists",
+ type: "character varying(64)",
+ maxLength: 64,
+ nullable: false,
+ defaultValue: "");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "OriginalPictureKeyName",
+ table: "Tracks");
+
+ migrationBuilder.DropColumn(
+ name: "OriginalPictureKeyName",
+ table: "PlayLists");
+ }
+ }
+}
diff --git a/Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs b/Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs
index f859ef0..73fc038 100644
--- a/Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs
+++ b/Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs
@@ -56,6 +56,11 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasMaxLength(50)
.HasColumnType("character varying(50)");
+ b.Property("OriginalPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
b.Property("SmallPictureKeyName")
.IsRequired()
.HasMaxLength(64)
@@ -205,6 +210,11 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasMaxLength(50)
.HasColumnType("character varying(50)");
+ b.Property("OriginalPictureKeyName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
b.Property("SmallPictureKeyName")
.IsRequired()
.HasMaxLength(64)