From 22a65b7efa03ee3dce778bd52c419fb21f749e8e Mon Sep 17 00:00:00 2001 From: XShine555 Date: Tue, 24 Mar 2026 14:45:20 +0100 Subject: [PATCH] Changes --- .../Configuration/PlayListConfiguration.cs | 3 + .../Configuration/TrackConfiguration.cs | 3 + .../Handlers/CreatePlayListCommandHandler.cs | 3 + .../Handler/CreateTrackCommandHandler.cs | 1 + Domain/Entities/PlayList.cs | 4 + Domain/Entities/Track.cs | 4 + Infrastructure/Infrastructure.csproj | 21 + .../GenerateAudioWorkflowPathsActivity.cs | 63 +++ .../Audio/TranscodeDashAudioActivity.cs | 89 ++++ .../Files/DownloadFileFromBucketActivity.cs | 100 +++++ .../Files}/RemoveFileFromBucketActivity.cs | 0 .../Activities/Files/TransferFilesToBucket.cs | 109 +++++ .../GeneratePictureWorkflowPathsActivity.cs | 76 ++++ .../Pictures/ResizePictureActivity.cs | 113 +++++ .../Pictures/UpdatePlayListPictureActivity.cs | 107 +++++ .../Pictures/UpdateTrackPictureActivity.cs | 107 +++++ .../DownloadFileFromBucketArguments.cs | 2 +- .../GenerateAudioWorkflowPathsArguments.cs | 6 + .../GeneratePictureWorkflowPathsArguments.cs | 9 + .../RemoveFileFromBucketArguments.cs | 0 .../Arguments/ResizePictureLocalArguments.cs | 4 +- .../Arguments/TranscodeDashAudioArguments.cs | 6 + .../TransferFilesToBucketArguments.cs | 4 +- .../UpdatePlayListPictureArguments.cs | 1 + .../Arguments/UpdateTrackPictureArguments.cs | 1 + .../Consumers/MessagingHelper.cs | 2 + .../Consumers/RemoveFileConsumer.cs | 23 + .../TranscodeAudioFromTrackConsumer.cs | 23 + .../UpdatePlayListPictureConsumer.cs | 23 + .../Consumers/UpdateTrackPictureConsumer.cs | 23 + .../MessagingDependencyInjection.cs | 173 ++++++++ .../EventBus}/MassTransitEventBus.cs | 0 .../Filters/ProcessTrackingConsumeFilter.cs | 0 .../Logs/UpdatePlayListPictureLog.cs | 7 +- .../MassTransit/Logs/UpdateTrackPictureLog.cs | 7 +- .../MassTransit/RoutingSlip/ActivityNames.cs | 18 + .../AudioWorkflowRoutingSlipBuilder.cs | 50 +++ .../PictureWorkflowRoutingSlipBuilder.cs | 159 +++++++ .../Builders/RemoveFileRoutingSlipBuilder.cs | 25 ++ .../Variables/RoutingSlipVariableNames.cs | 27 ++ .../Activities/Arguments/CopyFileArguments.cs | 8 - .../Arguments/ResizePictureArgument.cs | 10 - .../Arguments/TranscodeDashAudioArguments.cs | 4 - .../DownloadFileFromBucketActivity.cs | 80 ---- .../GenerateAudioWorkflowPathsActivity.cs | 0 .../GeneratePictureWorkflowPathsActivity.cs | 0 .../Logs/DownloadFileFromBucketLog.cs | 7 - .../Activities/Logs/TranscodeDashAudioLog.cs | 4 - .../Activities/ResizePictureActivity.cs | 87 ---- .../Activities/TranscodeDashAudioActivity.cs | 70 ---- .../Activities/TransferFilesToBucket.cs | 71 ---- .../UpdatePlayListPictureActivity.cs | 68 --- .../Activities/UpdateTrackPictureActivity.cs | 68 --- .../Messaging/Consumers/RemoveFileConsumer.cs | 28 -- .../TranscodeAudioFromTrackConsumer.cs | 44 -- .../UpdatePlayListPictureConsumer.cs | 112 ----- .../Consumers/UpdateTrackPictureConsumer.cs | 112 ----- .../Messaging/MessagingDependencyInjection.cs | 139 ------- .../Messaging/RoutingSlip/ActivityNames.cs | 0 .../AudioWorkflowRoutingSlipBuilder.cs | 0 .../PictureWorkflowRoutingSlipBuilder.cs | 0 .../Builders/RemoveFileRoutingSlipBuilder.cs | 0 .../Messaging/RoutingSlipVariableNames.cs | 0 ...ctureKeyNameToTrackAndPlayList.Designer.cs | 392 ++++++++++++++++++ ...riginalPictureKeyNameToTrackAndPlayList.cs | 42 ++ .../Migrations/DatabaseModelSnapshot.cs | 10 + 66 files changed, 1830 insertions(+), 922 deletions(-) create mode 100644 Infrastructure/MassTransit/Activities/Audio/GenerateAudioWorkflowPathsActivity.cs create mode 100644 Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs create mode 100644 Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs rename Infrastructure/{Messaging/Activities => MassTransit/Activities/Files}/RemoveFileFromBucketActivity.cs (100%) create mode 100644 Infrastructure/MassTransit/Activities/Files/TransferFilesToBucket.cs create mode 100644 Infrastructure/MassTransit/Activities/Pictures/GeneratePictureWorkflowPathsActivity.cs create mode 100644 Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs create mode 100644 Infrastructure/MassTransit/Activities/Pictures/UpdatePlayListPictureActivity.cs create mode 100644 Infrastructure/MassTransit/Activities/Pictures/UpdateTrackPictureActivity.cs rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/DownloadFileFromBucketArguments.cs (77%) create mode 100644 Infrastructure/MassTransit/Arguments/GenerateAudioWorkflowPathsArguments.cs create mode 100644 Infrastructure/MassTransit/Arguments/GeneratePictureWorkflowPathsArguments.cs rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/RemoveFileFromBucketArguments.cs (100%) rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/ResizePictureLocalArguments.cs (62%) create mode 100644 Infrastructure/MassTransit/Arguments/TranscodeDashAudioArguments.cs rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/TransferFilesToBucketArguments.cs (52%) rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/UpdatePlayListPictureArguments.cs (86%) rename Infrastructure/{Messaging/Activities => MassTransit}/Arguments/UpdateTrackPictureArguments.cs (86%) rename Infrastructure/{Messaging => MassTransit}/Consumers/MessagingHelper.cs (64%) create mode 100644 Infrastructure/MassTransit/Consumers/RemoveFileConsumer.cs create mode 100644 Infrastructure/MassTransit/Consumers/TranscodeAudioFromTrackConsumer.cs create mode 100644 Infrastructure/MassTransit/Consumers/UpdatePlayListPictureConsumer.cs create mode 100644 Infrastructure/MassTransit/Consumers/UpdateTrackPictureConsumer.cs create mode 100644 Infrastructure/MassTransit/DependencyInjection/MessagingDependencyInjection.cs rename Infrastructure/{Messaging => MassTransit/EventBus}/MassTransitEventBus.cs (100%) rename Infrastructure/{Messaging => MassTransit}/Filters/ProcessTrackingConsumeFilter.cs (100%) create mode 100644 Infrastructure/MassTransit/RoutingSlip/ActivityNames.cs create mode 100644 Infrastructure/MassTransit/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs create mode 100644 Infrastructure/MassTransit/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs create mode 100644 Infrastructure/MassTransit/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs create mode 100644 Infrastructure/MassTransit/RoutingSlip/Variables/RoutingSlipVariableNames.cs delete mode 100644 Infrastructure/Messaging/Activities/Arguments/CopyFileArguments.cs delete mode 100644 Infrastructure/Messaging/Activities/Arguments/ResizePictureArgument.cs delete mode 100644 Infrastructure/Messaging/Activities/Arguments/TranscodeDashAudioArguments.cs create mode 100644 Infrastructure/Messaging/Activities/GenerateAudioWorkflowPathsActivity.cs create mode 100644 Infrastructure/Messaging/Activities/GeneratePictureWorkflowPathsActivity.cs delete mode 100644 Infrastructure/Messaging/Activities/Logs/TranscodeDashAudioLog.cs create mode 100644 Infrastructure/Messaging/RoutingSlip/ActivityNames.cs create mode 100644 Infrastructure/Messaging/RoutingSlip/Builders/AudioWorkflowRoutingSlipBuilder.cs create mode 100644 Infrastructure/Messaging/RoutingSlip/Builders/PictureWorkflowRoutingSlipBuilder.cs create mode 100644 Infrastructure/Messaging/RoutingSlip/Builders/RemoveFileRoutingSlipBuilder.cs create mode 100644 Infrastructure/Messaging/RoutingSlipVariableNames.cs create mode 100644 Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.Designer.cs create mode 100644 Infrastructure/Persistence/Migrations/20260323233603_AddOriginalPictureKeyNameToTrackAndPlayList.cs 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)