Add editorconfig and analyzers, format backend code

This commit is contained in:
2026-09-24 04:54:17 +02:00
parent 87c3f97529
commit c8fe28b07b
367 changed files with 12179 additions and 12459 deletions
@@ -1,41 +1,40 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class AudioConfiguration
{
public class AudioConfiguration
{
public const string SectionName = "AudioTranscoder";
public const string SectionName = "AudioTranscoder";
[Required]
public TimeSpan TranscodingTimeout { get; set; } = TimeSpan.FromMinutes(5);
[Required]
public TimeSpan TranscodingTimeout { get; set; } = TimeSpan.FromMinutes(5);
[Required]
public FfmpegConfiguration Ffmpeg { get; set; } = new FfmpegConfiguration();
}
[Required]
public FfmpegConfiguration Ffmpeg { get; set; } = new FfmpegConfiguration();
}
public class FfmpegConfiguration
{
[Required]
public string ExecutableName { get; set; } = "ffmpeg";
public class FfmpegConfiguration
{
[Required]
public string ExecutableName { get; set; } = "ffmpeg";
[Required]
public string OutputFileName { get; set; } = "audio.m4a";
[Required]
public string OutputFileName { get; set; } = "audio.m4a";
[Required]
public string AudioCodec { get; set; } = "aac";
[Required]
public string AudioCodec { get; set; } = "aac";
[Required]
public string AudioBitrate { get; set; } = "128k";
[Required]
public string AudioBitrate { get; set; } = "128k";
[Range(1, 8)]
public int AudioChannels { get; set; } = 2;
[Range(1, 8)]
public int AudioChannels { get; set; } = 2;
[Range(8000, 192000)]
public int AudioSampleRate { get; set; } = 48000;
[Range(8000, 192000)]
public int AudioSampleRate { get; set; } = 48000;
[Required]
public string AudioProfile { get; set; } = "aac_low";
[Required]
public string AudioProfile { get; set; } = "aac_low";
public string AdditionalArguments { get; set; } = string.Empty;
}
}
public string AdditionalArguments { get; set; } = string.Empty;
}
@@ -1,12 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class DatabaseConfiguration
{
public class DatabaseConfiguration
{
public const string SectionName = "Database";
public const string SectionName = "Database";
[Required]
public required string ConnectionString { get; set; }
}
}
[Required]
public required string ConnectionString { get; set; }
}
@@ -1,25 +1,24 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class InfrastructureStorageConfiguration
{
public class InfrastructureStorageConfiguration
{
public const string SectionName = "InfrastructureStorage";
public const string SectionName = "InfrastructureStorage";
[Url]
[Required]
public required string Address { get; set; }
[Url]
[Required]
public required string Address { get; set; }
[Required]
public required string AccessKey { get; set; }
[Required]
public required string AccessKey { get; set; }
[Required]
public required string SecretAccessKey { get; set; }
[Required]
public required string SecretAccessKey { get; set; }
[Required]
public bool ForcePathStyle { get; set; }
[Required]
public bool ForcePathStyle { get; set; }
[Required]
public bool UseHttp { get; set; }
}
[Required]
public bool UseHttp { get; set; }
}
@@ -1,18 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class MassTransitConfiguration
{
public class MassTransitConfiguration
{
public const string SectionName = "MassTransit";
public const string SectionName = "MassTransit";
[Required]
public required string Address { get; set; }
[Required]
public required string Address { get; set; }
[Required]
public required string Username { get; set; }
[Required]
public required string Username { get; set; }
[Required]
public required string Password { get; set; }
}
[Required]
public required string Password { get; set; }
}
@@ -1,13 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
{
public class OpenTelemetryConfiguration
{
public const string SectionName = "OpenTelemetry";
namespace Musify.Infrastructure.Configuration;
[Url]
[Required]
public required string OtlpEndpoint { get; set; }
}
public class OpenTelemetryConfiguration
{
public const string SectionName = "OpenTelemetry";
[Url]
[Required]
public required string OtlpEndpoint { get; set; }
}
@@ -1,21 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class StreamTicketConfiguration
{
public class StreamTicketConfiguration
{
public const string SectionName = "StreamTicket";
public const string SectionName = "StreamTicket";
[Required]
public required string PrivateKeyPath { get; set; }
[Required]
public required string PrivateKeyPath { get; set; }
[Required]
public string Audience { get; set; } = "media-gateway";
[Required]
public string Audience { get; set; } = "media-gateway";
[Required]
public string Issuer { get; set; } = "musify-webapi";
[Required]
public string Issuer { get; set; } = "musify-webapi";
[Range(60, 86400)]
public int TicketTtlSeconds { get; set; } = 3600;
}
[Range(60, 86400)]
public int TicketTtlSeconds { get; set; } = 3600;
}
@@ -1,18 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Infrastructure.Configuration
namespace Musify.Infrastructure.Configuration;
public class WorkerConfiguration
{
public class WorkerConfiguration
{
public const string SectionName = "Workers";
public const string SectionName = "Workers";
[Required]
public WorkerRoutes Routes { get; set; } = new WorkerRoutes();
}
[Required]
public WorkerRoutes Routes { get; set; } = new WorkerRoutes();
}
public class WorkerRoutes
{
[Required]
public string TemporaryFilesDirectory { get; set; } = Path.GetTempPath();
}
public class WorkerRoutes
{
[Required]
public string TemporaryFilesDirectory { get; set; } = Path.GetTempPath();
}
@@ -4,35 +4,34 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.Mixes;
namespace Musify.Infrastructure.Jobs
namespace Musify.Infrastructure.Jobs;
public class DailyMixGenerationJob(
IDatabase database,
IMediator mediator,
ILogger<DailyMixGenerationJob> logger)
{
public class DailyMixGenerationJob(
IDatabase database,
IMediator mediator,
ILogger<DailyMixGenerationJob> logger)
public async Task RunAsync(CancellationToken cancellationToken)
{
public async Task RunAsync(CancellationToken cancellationToken)
var userIds = await database.ListeningHistories
.AsNoTracking()
.Where(history => history.IsCounted)
.Select(history => history.UserId)
.Distinct()
.ToListAsync(cancellationToken);
logger.LogInformation("Starting daily mix generation for {Count} users", userIds.Count);
foreach (var userId in userIds)
{
var userIds = await database.ListeningHistories
.AsNoTracking()
.Where(history => history.IsCounted)
.Select(history => history.UserId)
.Distinct()
.ToListAsync(cancellationToken);
logger.LogInformation("Starting daily mix generation for {Count} users", userIds.Count);
foreach (var userId in userIds)
var result = await mediator.Send(new GenerateMixesForUserCommand(userId), cancellationToken);
if (result.IsError)
{
var result = await mediator.Send(new GenerateMixesForUserCommand(userId), cancellationToken);
if (result.IsError)
{
logger.LogWarning("Failed to generate mixes for user {UserId}: {Error}",
userId, result.FirstError.Description);
}
logger.LogWarning("Failed to generate mixes for user {UserId}: {Error}",
userId, result.FirstError.Description);
}
logger.LogInformation("Finished daily mix generation");
}
logger.LogInformation("Finished daily mix generation");
}
}
@@ -1,13 +1,12 @@
using Mediator;
using Musify.Application.Tracks;
namespace Musify.Infrastructure.Jobs
namespace Musify.Infrastructure.Jobs;
public class ListeningHistoryCleanupJob(IMediator mediator)
{
public class ListeningHistoryCleanupJob(IMediator mediator)
public async Task RunAsync(CancellationToken cancellationToken)
{
public async Task RunAsync(CancellationToken cancellationToken)
{
await mediator.Send(new DeleteStaleUncountedListensCommand(), cancellationToken);
}
await mediator.Send(new DeleteStaleUncountedListensCommand(), cancellationToken);
}
}
@@ -4,68 +4,67 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Application.Contracts;
namespace Musify.Infrastructure.Jobs
namespace Musify.Infrastructure.Jobs;
public class TemporalUploadsCleanUpJob(
IServiceScopeFactory scopeFactory,
ILogger<TemporalUploadsCleanUpJob> logger,
ApplicationStorageConfiguration storageConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
{
public class TemporalUploadsCleanUpJob(
IServiceScopeFactory scopeFactory,
ILogger<TemporalUploadsCleanUpJob> logger,
ApplicationStorageConfiguration storageConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
logger.LogInformation("TemporalUploadsCleanUpJob started (interval: {Interval}s, prefix: {Prefix})",
uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds,
uploadIntentConfiguration.TemporalRootPrefix);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds));
do
{
logger.LogInformation("TemporalUploadsCleanUpJob started (interval: {Interval}s, prefix: {Prefix} )",
uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds,
uploadIntentConfiguration.TemporalRootPrefix);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds));
do
try
{
try
{
await RunAsync(stoppingToken);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in TemporalUploadsCleanUpJob");
}
await RunAsync(stoppingToken);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in TemporalUploadsCleanUpJob");
}
}
while (await timer.WaitForNextTickAsync(stoppingToken));
}
private async Task RunAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
var cutOff = DateTime.UtcNow.AddDays(-uploadIntentConfiguration.TemporalUploadsRetentionDays);
var deleted = 0;
var errors = 0;
await foreach (var (key, lastModified) in storageService.ListObjectsAsync(
storageConfiguration.Bucket,
uploadIntentConfiguration.TemporalRootPrefix,
cancellationToken))
{
if (lastModified >= cutOff)
continue;
try
{
await storageService.RemoveFileAsync(storageConfiguration.Bucket, key, cancellationToken);
deleted++;
}
catch (Exception exception)
{
errors++;
logger.LogWarning(exception, "Failed to delete orphaned temp object {Key}", key);
}
while (await timer.WaitForNextTickAsync(stoppingToken));
}
async Task RunAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
var cutOff = DateTime.UtcNow.AddDays(-uploadIntentConfiguration.TemporalUploadsRetentionDays);
var deleted = 0;
var errors = 0;
await foreach (var (key, lastModified) in storageService.ListObjectsAsync(
storageConfiguration.Bucket,
uploadIntentConfiguration.TemporalRootPrefix,
cancellationToken))
{
if (lastModified >= cutOff)
continue;
try
{
await storageService.RemoveFileAsync(storageConfiguration.Bucket, key, cancellationToken);
deleted++;
}
catch (Exception exception)
{
errors++;
logger.LogWarning(exception, "Failed to delete orphaned temp object {Key}", key);
}
}
if (deleted > 0 || errors > 0)
logger.LogInformation("TemporalUploadsCleanUpJob: deleted {Deleted} orphaned objects, {Errors} errors", deleted, errors);
}
if (deleted > 0 || errors > 0)
logger.LogInformation("TemporalUploadsCleanUpJob: deleted {Deleted} orphaned objects, {Errors} errors", deleted, errors);
}
}
@@ -3,71 +3,70 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
namespace Musify.Infrastructure.Jobs
namespace Musify.Infrastructure.Jobs;
public class UploadIntentExpirationJob(
IServiceScopeFactory scopeFactory,
ILogger<UploadIntentExpirationJob> logger,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
{
public class UploadIntentExpirationJob(
IServiceScopeFactory scopeFactory,
ILogger<UploadIntentExpirationJob> logger,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
logger.LogInformation("UploadIntentExpirationJob started (interval: {Interval}s)", uploadIntentConfiguration.ExpirationJobIntervalSeconds);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(uploadIntentConfiguration.ExpirationJobIntervalSeconds));
do
{
logger.LogInformation("UploadIntentExpirationJob started (interval: {Interval}s)", uploadIntentConfiguration.ExpirationJobIntervalSeconds);
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(uploadIntentConfiguration.ExpirationJobIntervalSeconds));
do
try
{
try
{
await RunAsync(stoppingToken);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in UploadIntentExpirationJob");
}
await RunAsync(stoppingToken);
}
while (await timer.WaitForNextTickAsync(stoppingToken));
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in UploadIntentExpirationJob");
}
}
while (await timer.WaitForNextTickAsync(stoppingToken));
}
private async Task RunAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
var database = scope.ServiceProvider.GetRequiredService<IDatabase>();
var now = DateTime.UtcNow;
var expiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Issued && i.ExpiresAt < now)
.ToListAsync(cancellationToken);
if (expiredIntents.Count > 0)
{
foreach (var intent in expiredIntents)
intent.Status = UploadIntentStatus.Expired;
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Marked {Count} upload intents as Expired", expiredIntents.Count);
}
async Task RunAsync(CancellationToken cancellationToken)
if (uploadIntentConfiguration.ExpiredIntentsRetentionDays >= 0)
{
await using var scope = scopeFactory.CreateAsyncScope();
var database = scope.ServiceProvider.GetRequiredService<IDatabase>();
var retentionCutoff = now.AddDays(-uploadIntentConfiguration.ExpiredIntentsRetentionDays);
var now = DateTime.UtcNow;
var expiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Issued && i.ExpiresAt < now)
var oldExpiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Expired && i.ExpiresAt < retentionCutoff)
.ToListAsync(cancellationToken);
if (expiredIntents.Count > 0)
if (oldExpiredIntents.Count > 0)
{
foreach (var intent in expiredIntents)
intent.Status = UploadIntentStatus.Expired;
database.UploadIntents.RemoveRange(oldExpiredIntents);
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Marked {Count} upload intents as Expired", expiredIntents.Count);
}
if (uploadIntentConfiguration.ExpiredIntentsRetentionDays >= 0)
{
var retentionCutoff = now.AddDays(-uploadIntentConfiguration.ExpiredIntentsRetentionDays);
var oldExpiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Expired && i.ExpiresAt < retentionCutoff)
.ToListAsync(cancellationToken);
if (oldExpiredIntents.Count > 0)
{
database.UploadIntents.RemoveRange(oldExpiredIntents);
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Deleted {Count} old Expired upload intents past retention window", oldExpiredIntents.Count);
}
logger.LogInformation("Deleted {Count} old Expired upload intents past retention window", oldExpiredIntents.Count);
}
}
}
@@ -5,28 +5,27 @@ using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Albums
namespace Musify.Infrastructure.MassTransit.Activities.Albums;
internal class MarkAlbumAsFailedActivity(
IDatabase database,
ILogger<MarkAlbumAsFailedActivity> logger)
: IExecuteActivity<MarkAlbumAsFailedArguments>
{
internal class MarkAlbumAsFailedActivity(
IDatabase database,
ILogger<MarkAlbumAsFailedActivity> logger)
: IExecuteActivity<MarkAlbumAsFailedArguments>
public const string ExecuteEndpointName = "mark-album-as-failed";
public async Task<ExecutionResult> Execute(ExecuteContext<MarkAlbumAsFailedArguments> executeContext)
{
public const string ExecuteEndpointName = "mark-album-as-failed";
var album = await database.Albums
.SingleOrDefaultAsync(a => a.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<MarkAlbumAsFailedArguments> executeContext)
if (album != null)
{
var album = await database.Albums
.SingleOrDefaultAsync(a => a.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken);
if (album != null)
{
album.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked album {AlbumId} as failed", executeContext.Arguments.AlbumId);
}
return executeContext.Completed();
album.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked album {AlbumId} as failed", executeContext.Arguments.AlbumId);
}
return executeContext.Completed();
}
}
@@ -3,32 +3,31 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Albums
namespace Musify.Infrastructure.MassTransit.Activities.Albums;
internal class PublishAlbumPictureProcessingEventActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishAlbumPictureProcessingEventActivity> logger)
: IExecuteActivity<PublishAlbumPictureProcessingEventArguments>
{
internal class PublishAlbumPictureProcessingEventActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishAlbumPictureProcessingEventActivity> logger)
: IExecuteActivity<PublishAlbumPictureProcessingEventArguments>
public const string ExecuteEndpointName = "publish-album-picture-processing-event";
public async Task<ExecutionResult> Execute(ExecuteContext<PublishAlbumPictureProcessingEventArguments> executeContext)
{
public const string ExecuteEndpointName = "publish-album-picture-processing-event";
var arguments = executeContext.Arguments;
public async Task<ExecutionResult> Execute(ExecuteContext<PublishAlbumPictureProcessingEventArguments> executeContext)
{
var arguments = executeContext.Arguments;
await publishEndpoint.Publish(
new UpdateAlbumPictureEvent(
arguments.AlbumId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
await publishEndpoint.Publish(
new UpdateAlbumPictureEvent(
arguments.AlbumId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
logger.LogInformation("Published picture processing event for album {AlbumId}", arguments.AlbumId);
logger.LogInformation("Published picture processing event for album {AlbumId}", arguments.AlbumId);
return executeContext.Completed();
}
return executeContext.Completed();
}
}
@@ -6,68 +6,67 @@ using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Audio
namespace Musify.Infrastructure.MassTransit.Activities.Audio;
internal class GenerateAudioWorkflowPathsActivity(
IDatabase database,
ILogger<GenerateAudioWorkflowPathsActivity> logger)
: IExecuteActivity<GenerateAudioWorkflowPathsArguments>
{
internal class GenerateAudioWorkflowPathsActivity(
IDatabase database,
ILogger<GenerateAudioWorkflowPathsActivity> logger)
: IExecuteActivity<GenerateAudioWorkflowPathsArguments>
public const string ExecuteEndpointName = "generate-audio-workflow-paths";
public async Task<ExecutionResult> Execute(ExecuteContext<GenerateAudioWorkflowPathsArguments> executeContext)
{
public const string ExecuteEndpointName = "generate-audio-workflow-paths";
public async Task<ExecutionResult> Execute(ExecuteContext<GenerateAudioWorkflowPathsArguments> executeContext)
Track track;
try
{
Track track;
try
var getTrack = await database.Tracks.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found.");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track for TrackId {TrackId}",
executeContext.Arguments.TrackId);
throw;
}
try
{
track.Audio.TranscodeStatus = ProcessingStatus.Processing;
var folderName = Guid.NewGuid().ToString();
var sourceFileName = Path.GetFileName(executeContext.Arguments.SourceKey);
var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, folderName);
var sourceFilePath = Path.Combine(workingDirectory, sourceFileName);
Directory.CreateDirectory(workingDirectory);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogDebug("Generated audio workflow paths in {WorkingDirectory}", workingDirectory);
return executeContext.CompletedWithVariables(new Dictionary<string, object>
{
var getTrack = await database.Tracks.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found.");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track for TrackId {TrackId}",
executeContext.Arguments.TrackId);
throw;
}
[RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory,
[RoutingSlipVariableNames.Audio.SourceFilePath] = sourceFilePath,
[RoutingSlipVariableNames.Audio.TranscodedDirectory] = workingDirectory
});
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to generate audio workflow paths for {SourceKey}",
executeContext.Arguments.SourceKey);
try
{
track.Audio.TranscodeStatus = ProcessingStatus.Processing;
var folderName = Guid.NewGuid().ToString();
var sourceFileName = Path.GetFileName(executeContext.Arguments.SourceKey);
var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, folderName);
var sourceFilePath = Path.Combine(workingDirectory, sourceFileName);
Directory.CreateDirectory(workingDirectory);
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogDebug("Generated audio workflow paths in {WorkingDirectory}", workingDirectory);
return executeContext.CompletedWithVariables(new Dictionary<string, object>
{
[RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory,
[RoutingSlipVariableNames.Audio.SourceFilePath] = sourceFilePath,
[RoutingSlipVariableNames.Audio.TranscodedDirectory] = workingDirectory
} );
}
catch (Exception exception)
catch (Exception dbException)
{
logger.LogError(exception, "Failed to generate audio workflow paths for {SourceKey}",
executeContext.Arguments.SourceKey);
try
{
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
}
catch (Exception dbException)
{
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
}
}
}
}
@@ -7,92 +7,96 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Audio
namespace Musify.Infrastructure.MassTransit.Activities.Audio;
internal class TranscodeAudioActivity(
IDatabase database,
IAudioTranscoderService audioTranscoder,
ILogger<TranscodeAudioActivity> logger)
: IActivity<TranscodeAudioArguments, TranscodeAudioLog>
{
internal class TranscodeAudioActivity(
IDatabase database,
IAudioTranscoderService audioTranscoder,
ILogger<TranscodeAudioActivity> logger)
: IActivity<TranscodeAudioArguments, TranscodeAudioLog>
public const string ExecuteEndpointName = "transcode-audio";
public async Task<ExecutionResult> Execute(ExecuteContext<TranscodeAudioArguments> executeContext)
{
public const string ExecuteEndpointName = "transcode-audio";
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.SourceFilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var workingDirectory = executeContext.GetVariable<string>(executeContext.Arguments.WorkingDirectoryVariable);
ArgumentNullException.ThrowIfNull(workingDirectory, nameof(workingDirectory));
public async Task<ExecutionResult> Execute(ExecuteContext<TranscodeAudioArguments> executeContext)
Track track;
try
{
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.SourceFilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var workingDirectory = executeContext.GetVariable<string>(executeContext.Arguments.WorkingDirectoryVariable);
ArgumentNullException.ThrowIfNull(workingDirectory, nameof(workingDirectory));
Track track;
try
{
var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with Id {executeContext.Arguments.TrackId} not found");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track with Id {TrackId} from database", executeContext.Arguments.TrackId);
throw;
}
try
{
AudioTranscodeResult transcodeResult;
await using (var fileStream = File.OpenRead(sourceFilePath))
{
transcodeResult = await audioTranscoder.TranscodeToAudioFileAsync(
fileStream,
workingDirectory,
executeContext.CancellationToken);
}
if (transcodeResult.StatusCode != 0)
throw new InvalidOperationException($"ffmpeg transcoding failed with exit code {transcodeResult.StatusCode}.");
File.Delete(sourceFilePath);
var log = new TranscodeAudioLog(workingDirectory);
return executeContext.CompletedWithVariables(log, new Dictionary<string, object>
{
[RoutingSlipVariableNames.Audio.DurationSeconds] = (int)Math.Round(transcodeResult.Duration.TotalSeconds)
} );
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to transcode {SourceFilePath}", sourceFilePath);
try
{
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
}
catch (Exception dbException)
{
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
}
var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with Id {executeContext.Arguments.TrackId} not found");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track with Id {TrackId} from database", executeContext.Arguments.TrackId);
throw;
}
public Task<CompensationResult> Compensate(CompensateContext<TranscodeAudioLog> compensateContext)
try
{
AudioTranscodeResult transcodeResult;
await using (var fileStream = File.OpenRead(sourceFilePath))
{
transcodeResult = await audioTranscoder.TranscodeToAudioFileAsync(
fileStream,
workingDirectory,
executeContext.CancellationToken);
}
if (transcodeResult.StatusCode != 0)
throw new InvalidOperationException($"ffmpeg transcoding failed with exit code {transcodeResult.StatusCode}.");
File.Delete(sourceFilePath);
var log = new TranscodeAudioLog(workingDirectory);
return executeContext.CompletedWithVariables(log, new Dictionary<string, object>
{
[RoutingSlipVariableNames.Audio.DurationSeconds] = (int)Math.Round(transcodeResult.Duration.TotalSeconds)
});
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to transcode {SourceFilePath}", sourceFilePath);
try
{
if (Directory.Exists(compensateContext.Log.WorkingDirectory))
Directory.Delete(compensateContext.Log.WorkingDirectory, recursive: true);
else
logger.LogWarning("Working directory {WorkingDirectory} not found during compensation",
compensateContext.Log.WorkingDirectory);
return Task.FromResult(compensateContext.Compensated());
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
}
catch (Exception exception)
catch (Exception dbException)
{
logger.LogError(exception, "Failed to compensate transcoded audio directory {WorkingDirectory}", compensateContext.Log.WorkingDirectory);
return Task.FromResult(compensateContext.Failed(exception));
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
}
}
public Task<CompensationResult> Compensate(CompensateContext<TranscodeAudioLog> compensateContext)
{
try
{
if (Directory.Exists(compensateContext.Log.WorkingDirectory))
{
Directory.Delete(compensateContext.Log.WorkingDirectory, recursive: true);
}
else
{
logger.LogWarning("Working directory {WorkingDirectory} not found during compensation",
compensateContext.Log.WorkingDirectory);
}
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate transcoded audio directory {WorkingDirectory}", compensateContext.Log.WorkingDirectory);
return Task.FromResult(compensateContext.Failed(exception));
}
}
}
@@ -8,109 +8,108 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Audio
namespace Musify.Infrastructure.MassTransit.Activities.Audio;
internal class UpdateTrackAudioActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateTrackAudioActivity> logger)
: IActivity<UpdateTrackAudioArguments, UpdateTrackAudioLog>
{
internal class UpdateTrackAudioActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateTrackAudioActivity> logger)
: IActivity<UpdateTrackAudioArguments, UpdateTrackAudioLog>
public const string ExecuteEndpointName = "update-track-audio";
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateTrackAudioArguments> executeContext)
{
public const string ExecuteEndpointName = "update-track-audio";
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateTrackAudioArguments> executeContext)
Track track;
try
{
Track track;
try
{
var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track {TrackId}",
executeContext.Arguments.TrackId);
throw;
}
var audioFolderKey = executeContext.Arguments.AudioFolderKey;
logger.LogDebug("Resolved audio folder key: {AudioFolderKey}", audioFolderKey);
if (string.IsNullOrWhiteSpace(audioFolderKey))
{
logger.LogWarning("Audio folder key is empty for track {TrackId}; skipping update", track.Id);
await publishEndpoint.Publish(
new TrackAudioProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed();
}
try
{
var durationSeconds = executeContext.GetVariable<double>(executeContext.Arguments.DurationSecondsVariable)
?? throw new InvalidOperationException($"Missing routing slip variable {executeContext.Arguments.DurationSecondsVariable}");
var log = new UpdateTrackAudioLog(
track.Id,
track.Audio.FolderName,
track.DurationSeconds);
track.Audio.FolderName = Path.GetFileName(audioFolderKey);
track.DurationSeconds = durationSeconds;
track.Audio.TranscodeStatus = ProcessingStatus.Completed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated track {TrackId} audio", executeContext.Arguments.TrackId);
await publishEndpoint.Publish(
new TrackAudioProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed(log);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to update track {TrackId} audio",
executeContext.Arguments.TrackId);
try
{
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
}
catch (Exception dbException)
{
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
}
var getTrack = await database.Tracks.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
track = getTrack ?? throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to retrieve track {TrackId}",
executeContext.Arguments.TrackId);
throw;
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateTrackAudioLog> compensateContext)
var audioFolderKey = executeContext.Arguments.AudioFolderKey;
logger.LogDebug("Resolved audio folder key: {AudioFolderKey}", audioFolderKey);
if (string.IsNullOrWhiteSpace(audioFolderKey))
{
logger.LogWarning("Audio folder key is empty for track {TrackId}; skipping update", track.Id);
await publishEndpoint.Publish(
new TrackAudioProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed();
}
try
{
var durationSeconds = executeContext.GetVariable<double>(executeContext.Arguments.DurationSecondsVariable)
?? throw new InvalidOperationException($"Missing routing slip variable {executeContext.Arguments.DurationSecondsVariable}");
var log = new UpdateTrackAudioLog(
track.Id,
track.Audio.FolderName,
track.DurationSeconds);
track.Audio.FolderName = Path.GetFileName(audioFolderKey);
track.DurationSeconds = durationSeconds;
track.Audio.TranscodeStatus = ProcessingStatus.Completed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated track {TrackId} audio", executeContext.Arguments.TrackId);
await publishEndpoint.Publish(
new TrackAudioProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed(log);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to update track {TrackId} audio",
executeContext.Arguments.TrackId);
try
{
var track = await database.Tracks.SingleOrDefaultAsync(t => t.Id == compensateContext.Log.TrackId, compensateContext.CancellationToken);
if (track == null)
throw new InvalidOperationException($"Track with id {compensateContext.Log.TrackId} not found");
track.Audio.FolderName = compensateContext.Log.PreviousAudioFolderName;
track.DurationSeconds = compensateContext.Log.PreviousDurationSeconds;
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(compensateContext.CancellationToken);
logger.LogInformation("Compensated track {TrackId} audio", compensateContext.Log.TrackId);
return compensateContext.Compensated();
await database.SaveChangesAsync(executeContext.CancellationToken);
}
catch (Exception exception)
catch (Exception dbException)
{
logger.LogError(exception, "Failed to compensate track {TrackId} audio",
compensateContext.Log.TrackId);
return compensateContext.Failed(exception);
logger.LogError(dbException, "Failed to update ProcessingStatus to Failed for track {TrackId}",
executeContext.Arguments.TrackId);
}
throw;
}
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateTrackAudioLog> compensateContext)
{
try
{
var track = await database.Tracks.SingleOrDefaultAsync(t => t.Id == compensateContext.Log.TrackId, compensateContext.CancellationToken);
if (track == null)
throw new InvalidOperationException($"Track with id {compensateContext.Log.TrackId} not found");
track.Audio.FolderName = compensateContext.Log.PreviousAudioFolderName;
track.DurationSeconds = compensateContext.Log.PreviousDurationSeconds;
track.Audio.TranscodeStatus = ProcessingStatus.Failed;
await database.SaveChangesAsync(compensateContext.CancellationToken);
logger.LogInformation("Compensated track {TrackId} audio", compensateContext.Log.TrackId);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate track {TrackId} audio",
compensateContext.Log.TrackId);
return compensateContext.Failed(exception);
}
}
}
@@ -4,58 +4,57 @@ using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class CopyFileInBucketActivity(
IStorageService storageHandler,
ILogger<CopyFileInBucketActivity> logger)
: IActivity<CopyFileInBucketArguments, CopyFileInBucketLog>
{
internal class CopyFileInBucketActivity(
IStorageService storageHandler,
ILogger<CopyFileInBucketActivity> logger)
: IActivity<CopyFileInBucketArguments, CopyFileInBucketLog>
public const string ExecuteEndpointName = "copy-file-in-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<CopyFileInBucketArguments> executeContext)
{
public const string ExecuteEndpointName = "copy-file-in-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<CopyFileInBucketArguments> executeContext)
try
{
try
{
await storageHandler.CopyFileAsync(
executeContext.Arguments.SourceBucket,
executeContext.Arguments.SourceKey,
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey,
executeContext.CancellationToken);
await storageHandler.CopyFileAsync(
executeContext.Arguments.SourceBucket,
executeContext.Arguments.SourceKey,
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey,
executeContext.CancellationToken);
return executeContext.Completed(new CopyFileInBucketLog(
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to copy {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
executeContext.Arguments.SourceBucket,
executeContext.Arguments.SourceKey,
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey);
throw;
}
return executeContext.Completed(new CopyFileInBucketLog(
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey));
}
public async Task<CompensationResult> Compensate(CompensateContext<CopyFileInBucketLog> compensateContext)
catch (Exception exception)
{
try
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey,
compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate copied file {Bucket}/{Key}",
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey);
return compensateContext.Failed(exception);
}
logger.LogError(exception, "Failed to copy {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
executeContext.Arguments.SourceBucket,
executeContext.Arguments.SourceKey,
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationKey);
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<CopyFileInBucketLog> compensateContext)
{
try
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey,
compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate copied file {Bucket}/{Key}",
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey);
return compensateContext.Failed(exception);
}
}
}
@@ -4,60 +4,59 @@ using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class DownloadFileFromBucketActivity(
IStorageService storageHandler,
ILogger<DownloadFileFromBucketActivity> logger)
: IActivity<DownloadFileFromBucketArguments, DownloadFileFromBucketLog>
{
internal class DownloadFileFromBucketActivity(
IStorageService storageHandler,
ILogger<DownloadFileFromBucketActivity> logger)
: IActivity<DownloadFileFromBucketArguments, DownloadFileFromBucketLog>
public const string ExecuteEndpointName = "download-file-from-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<DownloadFileFromBucketArguments> executeContext)
{
public const string ExecuteEndpointName = "download-file-from-bucket";
var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationPath, nameof(destinationPath));
public async Task<ExecutionResult> Execute(ExecuteContext<DownloadFileFromBucketArguments> executeContext)
try
{
var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationPath, nameof(destinationPath));
using var fileStream = await storageHandler.GetFileAsync(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
executeContext.CancellationToken);
try
{
using var fileStream = await storageHandler.GetFileAsync(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
executeContext.CancellationToken);
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
if (fileStream.CanSeek)
fileStream.Position = 0;
if (fileStream.CanSeek)
fileStream.Position = 0;
using var destinationStream = File.Create(destinationPath);
await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken);
return executeContext.Completed(new DownloadFileFromBucketLog(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
destinationPath));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to download file from bucket");
throw;
}
using var destinationStream = File.Create(destinationPath);
await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken);
return executeContext.Completed(new DownloadFileFromBucketLog(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
destinationPath));
}
public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromBucketLog> compensateContext)
catch (Exception exception)
{
try
{
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate downloaded file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
logger.LogError(exception, "Failed to download file from bucket");
throw;
}
}
}
public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromBucketLog> compensateContext)
{
try
{
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate downloaded file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
}
}
@@ -3,67 +3,66 @@ using Microsoft.Extensions.Logging;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class DownloadFileFromUrlActivity(
IHttpClientFactory httpClientFactory,
ILogger<DownloadFileFromUrlActivity> logger)
: IActivity<DownloadFileFromUrlArguments, DownloadFileFromUrlLog>
{
internal class DownloadFileFromUrlActivity(
IHttpClientFactory httpClientFactory,
ILogger<DownloadFileFromUrlActivity> logger)
: IActivity<DownloadFileFromUrlArguments, DownloadFileFromUrlLog>
public const string ExecuteEndpointName = "download-file-from-url";
public async Task<ExecutionResult> Execute(ExecuteContext<DownloadFileFromUrlArguments> executeContext)
{
public const string ExecuteEndpointName = "download-file-from-url";
var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationPath, nameof(destinationPath));
public async Task<ExecutionResult> Execute(ExecuteContext<DownloadFileFromUrlArguments> executeContext)
try
{
var destinationPath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationPath, nameof(destinationPath));
var httpClient = httpClientFactory.CreateClient();
try
using var response = await httpClient.GetAsync(
executeContext.Arguments.SourceUrl,
HttpCompletionOption.ResponseHeadersRead,
executeContext.CancellationToken);
response.EnsureSuccessStatusCode();
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
await using (var destinationStream = File.Create(destinationPath))
{
var httpClient = httpClientFactory.CreateClient();
using var response = await httpClient.GetAsync(
executeContext.Arguments.SourceUrl,
HttpCompletionOption.ResponseHeadersRead,
executeContext.CancellationToken);
response.EnsureSuccessStatusCode();
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrWhiteSpace(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
await using (var destinationStream = File.Create(destinationPath))
{
await response.Content.CopyToAsync(destinationStream, executeContext.CancellationToken);
}
logger.LogInformation("Downloaded {SourceUrl} to {DestinationPath}",
executeContext.Arguments.SourceUrl, destinationPath);
return executeContext.Completed(new DownloadFileFromUrlLog(
executeContext.Arguments.SourceUrl,
destinationPath));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to download file from {SourceUrl}", executeContext.Arguments.SourceUrl);
throw;
await response.Content.CopyToAsync(destinationStream, executeContext.CancellationToken);
}
logger.LogInformation("Downloaded {SourceUrl} to {DestinationPath}",
executeContext.Arguments.SourceUrl, destinationPath);
return executeContext.Completed(new DownloadFileFromUrlLog(
executeContext.Arguments.SourceUrl,
destinationPath));
}
public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromUrlLog> compensateContext)
catch (Exception exception)
{
try
{
if (File.Exists(compensateContext.Log.DestinationFilePath))
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate downloaded file {DestinationFilePath}",
compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
logger.LogError(exception, "Failed to download file from {SourceUrl}", executeContext.Arguments.SourceUrl);
throw;
}
}
public Task<CompensationResult> Compensate(CompensateContext<DownloadFileFromUrlLog> compensateContext)
{
try
{
if (File.Exists(compensateContext.Log.DestinationFilePath))
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate downloaded file {DestinationFilePath}",
compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
}
}
@@ -1,46 +1,45 @@
using MassTransit;
using Amazon.S3;
using Microsoft.Extensions.Logging;
using Musify.Infrastructure.MassTransit.Arguments;
using System.Net;
using Amazon.S3;
using MassTransit;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class RemoveFileFromBucketActivity(
IStorageService storageHandler,
ILogger<RemoveFileFromBucketActivity> logger)
: IExecuteActivity<RemoveFileFromBucketArguments>
{
internal class RemoveFileFromBucketActivity(
IStorageService storageHandler,
ILogger<RemoveFileFromBucketActivity> logger)
: IExecuteActivity<RemoveFileFromBucketArguments>
{
public const string ExecuteEndpointName = "remove-file-from-bucket";
public const string ExecuteEndpointName = "remove-file-from-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<RemoveFileFromBucketArguments> executeContext)
public async Task<ExecutionResult> Execute(ExecuteContext<RemoveFileFromBucketArguments> executeContext)
{
try
{
try
{
await storageHandler.RemoveFileAsync(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
executeContext.CancellationToken);
return executeContext.Completed();
}
catch (AmazonS3Exception exception) when (
exception.StatusCode == HttpStatusCode.NotFound
|| string.Equals(exception.ErrorCode, "NoSuchKey", StringComparison.OrdinalIgnoreCase))
{
logger.LogInformation(
"Key {Key} not found in bucket {Bucket}, skipping",
executeContext.Arguments.Key,
executeContext.Arguments.Bucket);
return executeContext.Completed();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to remove {Key} from bucket {Bucket}",
executeContext.Arguments.Key,
executeContext.Arguments.Bucket);
throw;
}
await storageHandler.RemoveFileAsync(
executeContext.Arguments.Bucket,
executeContext.Arguments.Key,
executeContext.CancellationToken);
return executeContext.Completed();
}
catch (AmazonS3Exception exception) when (
exception.StatusCode == HttpStatusCode.NotFound
|| string.Equals(exception.ErrorCode, "NoSuchKey", StringComparison.OrdinalIgnoreCase))
{
logger.LogInformation(
"Key {Key} not found in bucket {Bucket}, skipping",
executeContext.Arguments.Key,
executeContext.Arguments.Bucket);
return executeContext.Completed();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to remove {Key} from bucket {Bucket}",
executeContext.Arguments.Key,
executeContext.Arguments.Bucket);
throw;
}
}
}
}
@@ -4,62 +4,61 @@ using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class TransferFilesToBucketActivity(
IStorageService storageHandler,
ILogger<TransferFilesToBucketActivity> logger)
: IActivity<TransferFilesToBucketArguments, TransferFilesToBucketLog>
{
internal class TransferFilesToBucketActivity(
IStorageService storageHandler,
ILogger<TransferFilesToBucketActivity> logger)
: IActivity<TransferFilesToBucketArguments, TransferFilesToBucketLog>
public const string ExecuteEndpointName = "transfer-files-to-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<TransferFilesToBucketArguments> executeContext)
{
public const string ExecuteEndpointName = "transfer-files-to-bucket";
var folderPath = executeContext.GetVariable<string>(executeContext.Arguments.SourceDirectoryVariable);
ArgumentNullException.ThrowIfNull(folderPath, nameof(folderPath));
var destinationKey = executeContext.Arguments.DestinationKey;
ArgumentNullException.ThrowIfNull(destinationKey, nameof(destinationKey));
public async Task<ExecutionResult> Execute(ExecuteContext<TransferFilesToBucketArguments> executeContext)
try
{
var folderPath = executeContext.GetVariable<string>(executeContext.Arguments.SourceDirectoryVariable);
ArgumentNullException.ThrowIfNull(folderPath, nameof(folderPath));
var destinationKey = executeContext.Arguments.DestinationKey;
ArgumentNullException.ThrowIfNull(destinationKey, nameof(destinationKey));
var uploadedKeys = await storageHandler.TransferFilesAsync(
folderPath,
executeContext.Arguments.DestinationBucket,
destinationKey,
executeContext.CancellationToken);
try
{
var uploadedKeys = await storageHandler.TransferFilesAsync(
folderPath,
executeContext.Arguments.DestinationBucket,
destinationKey,
executeContext.CancellationToken);
return executeContext.Completed(new TransferFilesToBucketLog(
executeContext.Arguments.DestinationBucket,
uploadedKeys.ToArray()));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to transfer files from {FolderPath} to {DestinationBucket}",
folderPath,
executeContext.Arguments.DestinationBucket);
throw;
}
return executeContext.Completed(new TransferFilesToBucketLog(
executeContext.Arguments.DestinationBucket,
uploadedKeys.ToArray()));
}
public async Task<CompensationResult> Compensate(CompensateContext<TransferFilesToBucketLog> compensateContext)
catch (Exception exception)
{
try
{
foreach (var uploadedKey in compensateContext.Log.UploadedKeys)
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
uploadedKey,
compensateContext.CancellationToken);
}
logger.LogError(exception, "Failed to transfer files from {FolderPath} to {DestinationBucket}",
folderPath,
executeContext.Arguments.DestinationBucket);
throw;
}
}
return compensateContext.Compensated();
}
catch (Exception exception)
public async Task<CompensationResult> Compensate(CompensateContext<TransferFilesToBucketLog> compensateContext)
{
try
{
foreach (var uploadedKey in compensateContext.Log.UploadedKeys)
{
logger.LogError(exception, "Failed to compensate file transfers to {DestinationBucket}", compensateContext.Log.DestinationBucket);
return compensateContext.Failed(exception);
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
uploadedKey,
compensateContext.CancellationToken);
}
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate file transfers to {DestinationBucket}", compensateContext.Log.DestinationBucket);
return compensateContext.Failed(exception);
}
}
}
@@ -4,58 +4,57 @@ using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Files
namespace Musify.Infrastructure.MassTransit.Activities.Files;
internal class UploadFileToBucketActivity(IStorageService storageHandler,
ILogger<UploadFileToBucketActivity> logger)
: IActivity<UploadFileToBucketArguments, UploadFileToBucketLog>
{
internal class UploadFileToBucketActivity(IStorageService storageHandler,
ILogger<UploadFileToBucketActivity> logger)
: IActivity<UploadFileToBucketArguments, UploadFileToBucketLog>
public const string ExecuteEndpointName = "upload-file-to-bucket";
public async Task<ExecutionResult> Execute(ExecuteContext<UploadFileToBucketArguments> executeContext)
{
public const string ExecuteEndpointName = "upload-file-to-bucket";
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.FilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var fileName = Path.GetFileName(sourceFilePath);
var destinationKey = string.Join('/', new[] { executeContext.Arguments.DestinationRoute, fileName }
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\')));
public async Task<ExecutionResult> Execute(ExecuteContext<UploadFileToBucketArguments> executeContext)
try
{
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.FilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var fileName = Path.GetFileName(sourceFilePath);
var destinationKey = string.Join('/', new[] { executeContext.Arguments.DestinationRoute, fileName }
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\')));
try
{
await storageHandler.UploadFileAsync(
sourceFilePath,
executeContext.Arguments.DestinationBucket,
destinationKey,
executeContext.CancellationToken);
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
executeContext.Arguments.DestinationBucket,
destinationKey));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to upload file to bucket");
throw;
}
await storageHandler.UploadFileAsync(
sourceFilePath,
executeContext.Arguments.DestinationBucket,
destinationKey,
executeContext.CancellationToken);
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
executeContext.Arguments.DestinationBucket,
destinationKey));
}
public async Task<CompensationResult> Compensate(CompensateContext<UploadFileToBucketLog> compensateContext)
catch (Exception exception)
{
try
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey,
compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Error compensating file upload for {Bucket}/{Key}",
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey);
return compensateContext.Failed(exception);
}
logger.LogError(exception, "Failed to upload file to bucket");
throw;
}
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UploadFileToBucketLog> compensateContext)
{
try
{
await storageHandler.RemoveFileAsync(
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey,
compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Error compensating file upload for {Bucket}/{Key}",
compensateContext.Log.DestinationBucket,
compensateContext.Log.DestinationKey);
return compensateContext.Failed(exception);
}
}
}
@@ -2,50 +2,49 @@ using MassTransit;
using Microsoft.Extensions.Logging;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Pictures
namespace Musify.Infrastructure.MassTransit.Activities.Pictures;
internal class GeneratePictureWorkflowPathsActivity(
ILogger<GeneratePictureWorkflowPathsActivity> logger)
: IExecuteActivity<GeneratePictureWorkflowPathsArguments>
{
internal class GeneratePictureWorkflowPathsActivity(
ILogger<GeneratePictureWorkflowPathsActivity> logger)
: IExecuteActivity<GeneratePictureWorkflowPathsArguments>
public const string ExecuteEndpointName = "generate-picture-workflow-paths";
internal const string ResizedPictureFileExtension = ".webp";
public async Task<ExecutionResult> Execute(ExecuteContext<GeneratePictureWorkflowPathsArguments> executeContext)
{
public const string ExecuteEndpointName = "generate-picture-workflow-paths";
internal const string ResizedPictureFileExtension = ".webp";
public async Task<ExecutionResult> Execute(ExecuteContext<GeneratePictureWorkflowPathsArguments> executeContext)
try
{
try
var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, Guid.NewGuid().ToString());
var sourceFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + Path.GetExtension(executeContext.Arguments.SourceKey));
var smallPictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
var mediumPictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
var largePictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
Directory.CreateDirectory(workingDirectory);
logger.LogDebug("Generated picture workflow paths in {WorkingDirectory}",
workingDirectory);
return executeContext.CompletedWithVariables(new Dictionary<string, object>
{
var workingDirectory = Path.Combine(executeContext.Arguments.TemporaryRootDirectory, Guid.NewGuid().ToString());
var sourceFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + Path.GetExtension(executeContext.Arguments.SourceKey));
var smallPictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
var mediumPictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
var largePictureFilePath = Path.Combine(
workingDirectory, Guid.NewGuid().ToString() + ResizedPictureFileExtension);
Directory.CreateDirectory(workingDirectory);
logger.LogDebug("Generated picture workflow paths in {WorkingDirectory}",
workingDirectory);
return executeContext.CompletedWithVariables(new Dictionary<string, object>
{
[RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory,
[RoutingSlipVariableNames.Picture.OriginalFilePath] = sourceFilePath,
[RoutingSlipVariableNames.Picture.SmallResizedFilePath] = smallPictureFilePath,
[RoutingSlipVariableNames.Picture.MediumResizedFilePath] = mediumPictureFilePath,
[RoutingSlipVariableNames.Picture.LargeResizedFilePath] = largePictureFilePath
} );
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to generate picture workflow paths for {SourceKey}",
executeContext.Arguments.SourceKey);
throw;
}
[RoutingSlipVariableNames.Workflow.TemporalDirectory] = workingDirectory,
[RoutingSlipVariableNames.Picture.OriginalFilePath] = sourceFilePath,
[RoutingSlipVariableNames.Picture.SmallResizedFilePath] = smallPictureFilePath,
[RoutingSlipVariableNames.Picture.MediumResizedFilePath] = mediumPictureFilePath,
[RoutingSlipVariableNames.Picture.LargeResizedFilePath] = largePictureFilePath
});
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to generate picture workflow paths for {SourceKey}",
executeContext.Arguments.SourceKey);
throw;
}
}
}
}
@@ -4,69 +4,68 @@ using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Pictures
namespace Musify.Infrastructure.MassTransit.Activities.Pictures;
internal class ResizePictureActivity(
IPictureService pictureHandler,
ILogger<ResizePictureActivity> logger)
: IActivity<ResizePictureLocalArguments, ResizePictureLog>
{
internal class ResizePictureActivity(
IPictureService pictureHandler,
ILogger<ResizePictureActivity> logger)
: IActivity<ResizePictureLocalArguments, ResizePictureLog>
public const string ExecuteEndpointName = "resize-picture";
public async Task<ExecutionResult> Execute(ExecuteContext<ResizePictureLocalArguments> executeContext)
{
public const string ExecuteEndpointName = "resize-picture";
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.SourceFilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var destinationFilePath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationFilePath, nameof(destinationFilePath));
public async Task<ExecutionResult> Execute(ExecuteContext<ResizePictureLocalArguments> executeContext)
try
{
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.SourceFilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var destinationFilePath = executeContext.GetVariable<string>(executeContext.Arguments.DestinationFilePathVariable);
ArgumentNullException.ThrowIfNull(destinationFilePath, nameof(destinationFilePath));
try
if (!File.Exists(sourceFilePath))
{
if (!File.Exists(sourceFilePath))
{
logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath);
throw new FileNotFoundException($"Source file not found: {sourceFilePath}");
}
await using var fileStream = File.OpenRead(sourceFilePath);
await using var resizedPicture = await pictureHandler.ResizePictureAsWebpAsync(
fileStream,
executeContext.Arguments.Width,
executeContext.Arguments.Height,
executeContext.CancellationToken);
var destinationDirectory = Path.GetDirectoryName(destinationFilePath);
if (!string.IsNullOrEmpty(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
await using var destinationStream = File.Create(destinationFilePath);
await resizedPicture.CopyToAsync(destinationStream, executeContext.CancellationToken);
logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}",
sourceFilePath,
destinationFilePath);
return executeContext.Completed(new ResizePictureLog(destinationFilePath));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to resize picture {SourceFilePath}", sourceFilePath);
throw;
logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath);
throw new FileNotFoundException($"Source file not found: {sourceFilePath}");
}
await using var fileStream = File.OpenRead(sourceFilePath);
await using var resizedPicture = await pictureHandler.ResizePictureAsWebpAsync(
fileStream,
executeContext.Arguments.Width,
executeContext.Arguments.Height,
executeContext.CancellationToken);
var destinationDirectory = Path.GetDirectoryName(destinationFilePath);
if (!string.IsNullOrEmpty(destinationDirectory))
Directory.CreateDirectory(destinationDirectory);
await using var destinationStream = File.Create(destinationFilePath);
await resizedPicture.CopyToAsync(destinationStream, executeContext.CancellationToken);
logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}",
sourceFilePath,
destinationFilePath);
return executeContext.Completed(new ResizePictureLog(destinationFilePath));
}
public Task<CompensationResult> Compensate(CompensateContext<ResizePictureLog> compensateContext)
catch (Exception exception)
{
try
{
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate resized file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
logger.LogError(exception, "Failed to resize picture {SourceFilePath}", sourceFilePath);
throw;
}
}
}
public Task<CompensationResult> Compensate(CompensateContext<ResizePictureLog> compensateContext)
{
try
{
File.Delete(compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Compensated());
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate resized file {DestinationFilePath}", compensateContext.Log.DestinationFilePath);
return Task.FromResult(compensateContext.Failed(exception));
}
}
}
@@ -6,108 +6,107 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Pictures
namespace Musify.Infrastructure.MassTransit.Activities.Pictures;
internal class UpdateAlbumPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateAlbumPictureActivity> logger)
: IActivity<UpdateAlbumPictureArguments, UpdateAlbumPictureLog>
{
internal class UpdateAlbumPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateAlbumPictureActivity> logger)
: IActivity<UpdateAlbumPictureArguments, UpdateAlbumPictureLog>
public const string ExecuteEndpointName = "update-album-picture";
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateAlbumPictureArguments> executeContext)
{
public const string ExecuteEndpointName = "update-album-picture";
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateAlbumPictureArguments> executeContext)
try
{
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
var album = await database.Albums.FindAsync(
[executeContext.Arguments.AlbumId],
cancellationToken: executeContext.CancellationToken);
try
if (album == null)
{
var album = await database.Albums.FindAsync(
[executeContext.Arguments.AlbumId],
cancellationToken: executeContext.CancellationToken);
if (album == null)
{
logger.LogWarning("Album {AlbumId} not found",
executeContext.Arguments.AlbumId);
throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} not found");
}
if (album.Pictures == null)
{
logger.LogWarning("Album {AlbumId} has no pending picture upload",
executeContext.Arguments.AlbumId);
throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} has no pending picture upload");
}
var log = new UpdateAlbumPictureLog(
album.Id,
album.Pictures.OriginalName,
album.Pictures.SmallName,
album.Pictures.MediumName,
album.Pictures.LargeName);
album.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
album.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
album.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
album.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
database.Albums.Update(album);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated album {AlbumId} pictures",
logger.LogWarning("Album {AlbumId} not found",
executeContext.Arguments.AlbumId);
await publishEndpoint.Publish(
new AlbumPictureProcessed(executeContext.Arguments.AlbumId),
executeContext.CancellationToken);
return executeContext.Completed();
throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} not found");
}
catch (Exception exception)
if (album.Pictures == null)
{
logger.LogError(exception, "Failed to update album {AlbumId} pictures",
logger.LogWarning("Album {AlbumId} has no pending picture upload",
executeContext.Arguments.AlbumId);
throw;
throw new InvalidOperationException($"Album with id {executeContext.Arguments.AlbumId} has no pending picture upload");
}
var log = new UpdateAlbumPictureLog(
album.Id,
album.Pictures.OriginalName,
album.Pictures.SmallName,
album.Pictures.MediumName,
album.Pictures.LargeName);
album.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
album.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
album.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
album.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
database.Albums.Update(album);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated album {AlbumId} pictures",
executeContext.Arguments.AlbumId);
await publishEndpoint.Publish(
new AlbumPictureProcessed(executeContext.Arguments.AlbumId),
executeContext.CancellationToken);
return executeContext.Completed();
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateAlbumPictureLog> compensateContext)
catch (Exception exception)
{
try
logger.LogError(exception, "Failed to update album {AlbumId} pictures",
executeContext.Arguments.AlbumId);
throw;
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateAlbumPictureLog> compensateContext)
{
try
{
var album = await database.Albums.FindAsync(
[compensateContext.Log.AlbumId],
cancellationToken: compensateContext.CancellationToken);
if (album == null)
{
var album = await database.Albums.FindAsync(
[compensateContext.Log.AlbumId],
cancellationToken: compensateContext.CancellationToken);
if (album == null)
{
return compensateContext.Compensated();
}
album.Pictures = new AlbumPictures
{
OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey,
MediumName = compensateContext.Log.PreviousMediumPictureKey,
LargeName = compensateContext.Log.PreviousLargePictureKey
};
database.Albums.Update(album);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
album.Pictures = new AlbumPictures
{
logger.LogError(exception, "Failed to compensate album {AlbumId} pictures", compensateContext.Log.AlbumId);
return compensateContext.Failed(exception);
}
OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey,
MediumName = compensateContext.Log.PreviousMediumPictureKey,
LargeName = compensateContext.Log.PreviousLargePictureKey
};
database.Albums.Update(album);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate album {AlbumId} pictures", compensateContext.Log.AlbumId);
return compensateContext.Failed(exception);
}
}
}
@@ -6,108 +6,107 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Pictures
namespace Musify.Infrastructure.MassTransit.Activities.Pictures;
internal class UpdatePlayListPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdatePlayListPictureActivity> logger)
: IActivity<UpdatePlayListPictureArguments, UpdatePlayListPictureLog>
{
internal class UpdatePlayListPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdatePlayListPictureActivity> logger)
: IActivity<UpdatePlayListPictureArguments, UpdatePlayListPictureLog>
public const string ExecuteEndpointName = "update-play-list-picture";
public async Task<ExecutionResult> Execute(ExecuteContext<UpdatePlayListPictureArguments> executeContext)
{
public const string ExecuteEndpointName = "update-play-list-picture";
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
public async Task<ExecutionResult> Execute(ExecuteContext<UpdatePlayListPictureArguments> executeContext)
try
{
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
var playList = await database.PlayLists.FindAsync(
[executeContext.Arguments.PlayListId],
cancellationToken: executeContext.CancellationToken);
try
if (playList == null)
{
var playList = await database.PlayLists.FindAsync(
[executeContext.Arguments.PlayListId],
cancellationToken: executeContext.CancellationToken);
if (playList == null)
{
logger.LogWarning("Playlist {PlayListId} not found",
executeContext.Arguments.PlayListId);
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found");
}
if (playList.Pictures == null)
{
logger.LogWarning("Playlist {PlayListId} has no pending picture upload",
executeContext.Arguments.PlayListId);
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} has no pending picture upload");
}
var log = new UpdatePlayListPictureLog(
playList.Id,
playList.Pictures.OriginalName,
playList.Pictures.SmallName,
playList.Pictures.MediumName,
playList.Pictures.LargeName);
playList.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
playList.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
playList.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
playList.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
database.PlayLists.Update(playList);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated playlist {PlayListId} pictures",
logger.LogWarning("Playlist {PlayListId} not found",
executeContext.Arguments.PlayListId);
await publishEndpoint.Publish(
new PlayListPictureProcessed(executeContext.Arguments.PlayListId),
executeContext.CancellationToken);
return executeContext.Completed();
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found");
}
catch (Exception exception)
if (playList.Pictures == null)
{
logger.LogError(exception, "Failed to update playlist {PlayListId} pictures",
logger.LogWarning("Playlist {PlayListId} has no pending picture upload",
executeContext.Arguments.PlayListId);
throw;
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} has no pending picture upload");
}
var log = new UpdatePlayListPictureLog(
playList.Id,
playList.Pictures.OriginalName,
playList.Pictures.SmallName,
playList.Pictures.MediumName,
playList.Pictures.LargeName);
playList.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
playList.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
playList.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
playList.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
database.PlayLists.Update(playList);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated playlist {PlayListId} pictures",
executeContext.Arguments.PlayListId);
await publishEndpoint.Publish(
new PlayListPictureProcessed(executeContext.Arguments.PlayListId),
executeContext.CancellationToken);
return executeContext.Completed();
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdatePlayListPictureLog> compensateContext)
catch (Exception exception)
{
try
{
var playList = await database.PlayLists.FindAsync(
[compensateContext.Log.PlayListId],
cancellationToken: compensateContext.CancellationToken);
if (playList == null)
{
return compensateContext.Compensated();
}
playList.Pictures = new PlayListPictures
{
OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey,
MediumName = compensateContext.Log.PreviousMediumPictureKey,
LargeName = compensateContext.Log.PreviousLargePictureKey
};
database.PlayLists.Update(playList);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate playlist {PlayListId} pictures", compensateContext.Log.PlayListId);
return compensateContext.Failed(exception);
}
logger.LogError(exception, "Failed to update playlist {PlayListId} pictures",
executeContext.Arguments.PlayListId);
throw;
}
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdatePlayListPictureLog> compensateContext)
{
try
{
var playList = await database.PlayLists.FindAsync(
[compensateContext.Log.PlayListId],
cancellationToken: compensateContext.CancellationToken);
if (playList == null)
{
return compensateContext.Compensated();
}
playList.Pictures = new PlayListPictures
{
OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey,
MediumName = compensateContext.Log.PreviousMediumPictureKey,
LargeName = compensateContext.Log.PreviousLargePictureKey
};
database.PlayLists.Update(playList);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate playlist {PlayListId} pictures", compensateContext.Log.PlayListId);
return compensateContext.Failed(exception);
}
}
}
@@ -6,98 +6,97 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.Pictures
namespace Musify.Infrastructure.MassTransit.Activities.Pictures;
internal class UpdateTrackPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateTrackPictureActivity> logger)
: IActivity<UpdateTrackPictureArguments, UpdateTrackPictureLog>
{
internal class UpdateTrackPictureActivity(
IDatabase database,
IPublishEndpoint publishEndpoint,
ILogger<UpdateTrackPictureActivity> logger)
: IActivity<UpdateTrackPictureArguments, UpdateTrackPictureLog>
public const string ExecuteEndpointName = "update-track-picture";
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateTrackPictureArguments> executeContext)
{
public const string ExecuteEndpointName = "update-track-picture";
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
public async Task<ExecutionResult> Execute(ExecuteContext<UpdateTrackPictureArguments> executeContext)
try
{
var smallResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.SmallPictureVariable);
ArgumentNullException.ThrowIfNull(smallResizedVariable, nameof(smallResizedVariable));
var mediumResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.MediumPictureVariable);
ArgumentNullException.ThrowIfNull(mediumResizedVariable, nameof(mediumResizedVariable));
var largeResizedVariable = executeContext.GetVariable<string>(executeContext.Arguments.LargePictureVariable);
ArgumentNullException.ThrowIfNull(largeResizedVariable, nameof(largeResizedVariable));
var track = await database.Tracks.FindAsync(
[executeContext.Arguments.TrackId],
cancellationToken: executeContext.CancellationToken);
try
if (track == null)
{
var track = await database.Tracks.FindAsync(
[executeContext.Arguments.TrackId],
cancellationToken: executeContext.CancellationToken);
if (track == null)
{
logger.LogWarning("Track {TrackId} not found",
executeContext.Arguments.TrackId);
throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
}
var log = new UpdateTrackPictureLog(
track.Id,
track.Pictures.OriginalName,
track.Pictures.SmallName,
track.Pictures.MediumName,
track.Pictures.LargeName);
track.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
track.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
track.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
track.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
track.Pictures.ProcessingStatus = ProcessingStatus.Completed;
database.Tracks.Update(track);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated track {TrackId} pictures",
logger.LogWarning("Track {TrackId} not found",
executeContext.Arguments.TrackId);
await publishEndpoint.Publish(
new TrackPictureProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to update track {TrackId} pictures",
executeContext.Arguments.TrackId);
throw;
throw new InvalidOperationException($"Track with id {executeContext.Arguments.TrackId} not found");
}
var log = new UpdateTrackPictureLog(
track.Id,
track.Pictures.OriginalName,
track.Pictures.SmallName,
track.Pictures.MediumName,
track.Pictures.LargeName);
track.Pictures.OriginalName = Path.GetFileName(executeContext.Arguments.OriginalPictureKey);
track.Pictures.SmallName = Path.GetFileName(smallResizedVariable);
track.Pictures.MediumName = Path.GetFileName(mediumResizedVariable);
track.Pictures.LargeName = Path.GetFileName(largeResizedVariable);
track.Pictures.ProcessingStatus = ProcessingStatus.Completed;
database.Tracks.Update(track);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Updated track {TrackId} pictures",
executeContext.Arguments.TrackId);
await publishEndpoint.Publish(
new TrackPictureProcessed(executeContext.Arguments.TrackId),
executeContext.CancellationToken);
return executeContext.Completed();
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateTrackPictureLog> compensateContext)
catch (Exception exception)
{
try
{
var track = await database.Tracks.FindAsync(
[compensateContext.Log.TrackId],
cancellationToken: compensateContext.CancellationToken);
if (track == null)
return compensateContext.Compensated();
track.Pictures.OriginalName = compensateContext.Log.PreviousOriginalPictureKey;
track.Pictures.SmallName = compensateContext.Log.PreviousSmallPictureKey;
track.Pictures.MediumName = compensateContext.Log.PreviousMediumPictureKey;
track.Pictures.LargeName = compensateContext.Log.PreviousLargePictureKey;
track.Pictures.ProcessingStatus = ProcessingStatus.Failed;
database.Tracks.Update(track);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate track {TrackId} pictures", compensateContext.Log.TrackId);
return compensateContext.Failed(exception);
}
logger.LogError(exception, "Failed to update track {TrackId} pictures",
executeContext.Arguments.TrackId);
throw;
}
}
}
public async Task<CompensationResult> Compensate(CompensateContext<UpdateTrackPictureLog> compensateContext)
{
try
{
var track = await database.Tracks.FindAsync(
[compensateContext.Log.TrackId],
cancellationToken: compensateContext.CancellationToken);
if (track == null)
return compensateContext.Compensated();
track.Pictures.OriginalName = compensateContext.Log.PreviousOriginalPictureKey;
track.Pictures.SmallName = compensateContext.Log.PreviousSmallPictureKey;
track.Pictures.MediumName = compensateContext.Log.PreviousMediumPictureKey;
track.Pictures.LargeName = compensateContext.Log.PreviousLargePictureKey;
track.Pictures.ProcessingStatus = ProcessingStatus.Failed;
database.Tracks.Update(track);
await database.SaveChangesAsync(compensateContext.CancellationToken);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate track {TrackId} pictures", compensateContext.Log.TrackId);
return compensateContext.Failed(exception);
}
}
}
@@ -4,33 +4,32 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists;
internal class DeletePlayListFromDbActivity(
IDatabase database,
ILogger<DeletePlayListFromDbActivity> logger)
: IExecuteActivity<DeletePlayListFromDbArguments>
{
internal class DeletePlayListFromDbActivity(
IDatabase database,
ILogger<DeletePlayListFromDbActivity> logger)
: IExecuteActivity<DeletePlayListFromDbArguments>
public const string ExecuteEndpointName = "delete-play-list-from-db";
public async Task<ExecutionResult> Execute(ExecuteContext<DeletePlayListFromDbArguments> executeContext)
{
public const string ExecuteEndpointName = "delete-play-list-from-db";
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<DeletePlayListFromDbArguments> executeContext)
if (playList == null)
{
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
if (playList == null)
{
logger.LogInformation(
"PlayList {PlayListId} not found while deleting from DB, skipping",
executeContext.Arguments.PlayListId);
return executeContext.Completed();
}
database.PlayLists.Remove(playList);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Deleted playlist {PlayListId} from DB", playList.Id);
logger.LogInformation(
"PlayList {PlayListId} not found while deleting from DB, skipping",
executeContext.Arguments.PlayListId);
return executeContext.Completed();
}
database.PlayLists.Remove(playList);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Deleted playlist {PlayListId} from DB", playList.Id);
return executeContext.Completed();
}
}
@@ -5,28 +5,27 @@ using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists;
internal class MarkPlayListAsFailedActivity(
IDatabase database,
ILogger<MarkPlayListAsFailedActivity> logger)
: IExecuteActivity<MarkPlayListAsFailedArguments>
{
internal class MarkPlayListAsFailedActivity(
IDatabase database,
ILogger<MarkPlayListAsFailedActivity> logger)
: IExecuteActivity<MarkPlayListAsFailedArguments>
public const string ExecuteEndpointName = "mark-play-list-as-failed";
public async Task<ExecutionResult> Execute(ExecuteContext<MarkPlayListAsFailedArguments> executeContext)
{
public const string ExecuteEndpointName = "mark-play-list-as-failed";
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<MarkPlayListAsFailedArguments> executeContext)
if (playList != null)
{
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
if (playList != null)
{
playList.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked playlist {PlayListId} as failed", executeContext.Arguments.PlayListId);
}
return executeContext.Completed();
playList.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked playlist {PlayListId} as failed", executeContext.Arguments.PlayListId);
}
return executeContext.Completed();
}
}
@@ -1,37 +1,36 @@
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists;
internal class MarkPlayListAsRemovingActivity(
IDatabase database,
ILogger<MarkPlayListAsRemovingActivity> logger)
: IExecuteActivity<MarkPlayListAsRemovingArguments>
{
internal class MarkPlayListAsRemovingActivity(
IDatabase database,
ILogger<MarkPlayListAsRemovingActivity> logger)
: IExecuteActivity<MarkPlayListAsRemovingArguments>
public const string ExecuteEndpointName = "mark-play-list-as-removing";
public async Task<ExecutionResult> Execute(ExecuteContext<MarkPlayListAsRemovingArguments> executeContext)
{
public const string ExecuteEndpointName = "mark-play-list-as-removing";
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<MarkPlayListAsRemovingArguments> executeContext)
if (playList == null)
{
var playList = await database.PlayLists
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.PlayListId, executeContext.CancellationToken);
if (playList == null)
{
logger.LogInformation(
"PlayList {PlayListId} not found while marking as removing, skipping",
executeContext.Arguments.PlayListId);
return executeContext.Completed();
}
playList.LifeCycleStatus = LifeCycleStatus.Removing;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked playlist {PlayListId} as removing", playList.Id);
logger.LogInformation(
"PlayList {PlayListId} not found while marking as removing, skipping",
executeContext.Arguments.PlayListId);
return executeContext.Completed();
}
playList.LifeCycleStatus = LifeCycleStatus.Removing;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked playlist {PlayListId} as removing", playList.Id);
return executeContext.Completed();
}
}
@@ -3,32 +3,31 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists
namespace Musify.Infrastructure.MassTransit.Activities.PlayLists;
internal class PublishPlayListPictureProcessingEventActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishPlayListPictureProcessingEventActivity> logger)
: IExecuteActivity<PublishPlayListPictureProcessingEventArguments>
{
internal class PublishPlayListPictureProcessingEventActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishPlayListPictureProcessingEventActivity> logger)
: IExecuteActivity<PublishPlayListPictureProcessingEventArguments>
public const string ExecuteEndpointName = "publish-play-list-picture-processing-event";
public async Task<ExecutionResult> Execute(ExecuteContext<PublishPlayListPictureProcessingEventArguments> executeContext)
{
public const string ExecuteEndpointName = "publish-play-list-picture-processing-event";
var arguments = executeContext.Arguments;
public async Task<ExecutionResult> Execute(ExecuteContext<PublishPlayListPictureProcessingEventArguments> executeContext)
{
var arguments = executeContext.Arguments;
await publishEndpoint.Publish(
new UpdatePlayListPictureEvent(
arguments.PlayListId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
await publishEndpoint.Publish(
new UpdatePlayListPictureEvent(
arguments.PlayListId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
logger.LogInformation("Published picture processing event for playlist {PlayListId}", arguments.PlayListId);
logger.LogInformation("Published picture processing event for playlist {PlayListId}", arguments.PlayListId);
return executeContext.Completed();
}
return executeContext.Completed();
}
}
@@ -4,33 +4,32 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Tracks
namespace Musify.Infrastructure.MassTransit.Activities.Tracks;
internal class DeleteTrackFromDbActivity(
IDatabase database,
ILogger<DeleteTrackFromDbActivity> logger)
: IExecuteActivity<DeleteTrackFromDbArguments>
{
internal class DeleteTrackFromDbActivity(
IDatabase database,
ILogger<DeleteTrackFromDbActivity> logger)
: IExecuteActivity<DeleteTrackFromDbArguments>
public const string ExecuteEndpointName = "delete-track-from-db";
public async Task<ExecutionResult> Execute(ExecuteContext<DeleteTrackFromDbArguments> executeContext)
{
public const string ExecuteEndpointName = "delete-track-from-db";
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<DeleteTrackFromDbArguments> executeContext)
if (track == null)
{
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
if (track == null)
{
logger.LogInformation(
"Track {TrackId} not found while deleting from DB, skipping",
executeContext.Arguments.TrackId);
return executeContext.Completed();
}
database.Tracks.Remove(track);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Deleted track {TrackId} from DB", track.Id);
logger.LogInformation(
"Track {TrackId} not found while deleting from DB, skipping",
executeContext.Arguments.TrackId);
return executeContext.Completed();
}
database.Tracks.Remove(track);
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Deleted track {TrackId} from DB", track.Id);
return executeContext.Completed();
}
}
@@ -5,28 +5,27 @@ using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Tracks
namespace Musify.Infrastructure.MassTransit.Activities.Tracks;
internal class MarkTrackAsFailedActivity(
IDatabase database,
ILogger<MarkTrackAsFailedActivity> logger)
: IExecuteActivity<MarkTrackAsFailedArguments>
{
internal class MarkTrackAsFailedActivity(
IDatabase database,
ILogger<MarkTrackAsFailedActivity> logger)
: IExecuteActivity<MarkTrackAsFailedArguments>
public const string ExecuteEndpointName = "mark-track-as-failed";
public async Task<ExecutionResult> Execute(ExecuteContext<MarkTrackAsFailedArguments> executeContext)
{
public const string ExecuteEndpointName = "mark-track-as-failed";
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<MarkTrackAsFailedArguments> executeContext)
if (track != null)
{
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
if (track != null)
{
track.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked track {TrackId} as failed", executeContext.Arguments.TrackId);
}
return executeContext.Completed();
track.LifeCycleStatus = LifeCycleStatus.Failed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked track {TrackId} as failed", executeContext.Arguments.TrackId);
}
return executeContext.Completed();
}
}
@@ -1,37 +1,36 @@
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Tracks
namespace Musify.Infrastructure.MassTransit.Activities.Tracks;
internal class MarkTrackAsRemovingActivity(
IDatabase database,
ILogger<MarkTrackAsRemovingActivity> logger)
: IExecuteActivity<MarkTrackAsRemovingArguments>
{
internal class MarkTrackAsRemovingActivity(
IDatabase database,
ILogger<MarkTrackAsRemovingActivity> logger)
: IExecuteActivity<MarkTrackAsRemovingArguments>
public const string ExecuteEndpointName = "mark-track-as-removing";
public async Task<ExecutionResult> Execute(ExecuteContext<MarkTrackAsRemovingArguments> executeContext)
{
public const string ExecuteEndpointName = "mark-track-as-removing";
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
public async Task<ExecutionResult> Execute(ExecuteContext<MarkTrackAsRemovingArguments> executeContext)
if (track == null)
{
var track = await database.Tracks
.SingleOrDefaultAsync(t => t.Id == executeContext.Arguments.TrackId, executeContext.CancellationToken);
if (track == null)
{
logger.LogInformation(
"Track {TrackId} not found while marking as removing, skipping",
executeContext.Arguments.TrackId);
return executeContext.Completed();
}
track.LifeCycleStatus = LifeCycleStatus.Removing;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked track {TrackId} as removing", track.Id);
logger.LogInformation(
"Track {TrackId} not found while marking as removing, skipping",
executeContext.Arguments.TrackId);
return executeContext.Completed();
}
track.LifeCycleStatus = LifeCycleStatus.Removing;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked track {TrackId} as removing", track.Id);
return executeContext.Completed();
}
}
@@ -3,41 +3,40 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.Arguments;
namespace Musify.Infrastructure.MassTransit.Activities.Tracks
namespace Musify.Infrastructure.MassTransit.Activities.Tracks;
internal class PublishTrackProcessingEventsActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishTrackProcessingEventsActivity> logger)
: IExecuteActivity<PublishTrackProcessingEventsArguments>
{
internal class PublishTrackProcessingEventsActivity(
IPublishEndpoint publishEndpoint,
ILogger<PublishTrackProcessingEventsActivity> logger)
: IExecuteActivity<PublishTrackProcessingEventsArguments>
public const string ExecuteEndpointName = "publish-track-processing-events";
public async Task<ExecutionResult> Execute(ExecuteContext<PublishTrackProcessingEventsArguments> executeContext)
{
public const string ExecuteEndpointName = "publish-track-processing-events";
var arguments = executeContext.Arguments;
public async Task<ExecutionResult> Execute(ExecuteContext<PublishTrackProcessingEventsArguments> executeContext)
{
var arguments = executeContext.Arguments;
await publishEndpoint.Publish(
new UpdateTrackPictureEvent(
arguments.TrackId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
await publishEndpoint.Publish(
new UpdateTrackPictureEvent(
arguments.TrackId,
arguments.Bucket,
arguments.FinalPictureKey,
arguments.Small,
arguments.Medium,
arguments.Large),
executeContext.CancellationToken);
await publishEndpoint.Publish(
new UpdateTrackAudioEvent(
arguments.TrackId,
arguments.Bucket,
arguments.FinalAudioKey,
arguments.Bucket,
arguments.AudioDestinationFolderKey),
executeContext.CancellationToken);
await publishEndpoint.Publish(
new UpdateTrackAudioEvent(
arguments.TrackId,
arguments.Bucket,
arguments.FinalAudioKey,
arguments.Bucket,
arguments.AudioDestinationFolderKey),
executeContext.CancellationToken);
logger.LogInformation("Published processing events for track {TrackId}", arguments.TrackId);
logger.LogInformation("Published processing events for track {TrackId}", arguments.TrackId);
return executeContext.Completed();
}
return executeContext.Completed();
}
}
@@ -6,57 +6,56 @@ using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
namespace Musify.Infrastructure.MassTransit.Activities.UploadIntents
{
internal class ConsumeUploadIntentsActivity(
IDatabase database,
ILogger<ConsumeUploadIntentsActivity> logger)
: IActivity<ConsumeUploadIntentsArguments, ConsumeUploadIntentsLog>
{
public const string ExecuteEndpointName = "consume-upload-intents";
namespace Musify.Infrastructure.MassTransit.Activities.UploadIntents;
public async Task<ExecutionResult> Execute(ExecuteContext<ConsumeUploadIntentsArguments> executeContext)
internal class ConsumeUploadIntentsActivity(
IDatabase database,
ILogger<ConsumeUploadIntentsActivity> logger)
: IActivity<ConsumeUploadIntentsArguments, ConsumeUploadIntentsLog>
{
public const string ExecuteEndpointName = "consume-upload-intents";
public async Task<ExecutionResult> Execute(ExecuteContext<ConsumeUploadIntentsArguments> executeContext)
{
var intentIds = executeContext.Arguments.IntentIds;
var intents = await database.UploadIntents
.Where(i => intentIds.Contains(i.Id))
.ToListAsync(executeContext.CancellationToken);
foreach (var intent in intents)
intent.Status = UploadIntentStatus.Consumed;
await database.SaveChangesAsync(executeContext.CancellationToken);
logger.LogInformation("Marked {Count} upload intent(s) as consumed", intents.Count);
return executeContext.Completed(new ConsumeUploadIntentsLog(intentIds));
}
public async Task<CompensationResult> Compensate(CompensateContext<ConsumeUploadIntentsLog> compensateContext)
{
try
{
var intentIds = executeContext.Arguments.IntentIds;
var intentIds = compensateContext.Log.IntentIds;
var intents = await database.UploadIntents
.Where(i => intentIds.Contains(i.Id))
.ToListAsync(executeContext.CancellationToken);
.ToListAsync(compensateContext.CancellationToken);
foreach (var intent in intents)
intent.Status = UploadIntentStatus.Consumed;
intent.Status = UploadIntentStatus.Issued;
await database.SaveChangesAsync(executeContext.CancellationToken);
await database.SaveChangesAsync(compensateContext.CancellationToken);
logger.LogInformation("Marked {Count} upload intent(s) as consumed", intents.Count);
logger.LogInformation("Reverted {Count} upload intent(s) back to Issued", intents.Count);
return executeContext.Completed(new ConsumeUploadIntentsLog(intentIds));
return compensateContext.Compensated();
}
public async Task<CompensationResult> Compensate(CompensateContext<ConsumeUploadIntentsLog> compensateContext)
catch (Exception exception)
{
try
{
var intentIds = compensateContext.Log.IntentIds;
var intents = await database.UploadIntents
.Where(i => intentIds.Contains(i.Id))
.ToListAsync(compensateContext.CancellationToken);
foreach (var intent in intents)
intent.Status = UploadIntentStatus.Issued;
await database.SaveChangesAsync(compensateContext.CancellationToken);
logger.LogInformation("Reverted {Count} upload intent(s) back to Issued", intents.Count);
return compensateContext.Compensated();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to compensate upload intent consumption");
return compensateContext.Failed(exception);
}
logger.LogError(exception, "Failed to compensate upload intent consumption");
return compensateContext.Failed(exception);
}
}
}
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record ConsumeUploadIntentsArguments(
Guid[] IntentIds);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record ConsumeUploadIntentsArguments(
Guid[] IntentIds);
@@ -1,8 +1,7 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record CopyFileInBucketArguments(
string SourceBucket,
string SourceKey,
string DestinationBucket,
string DestinationKey);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record CopyFileInBucketArguments(
string SourceBucket,
string SourceKey,
string DestinationBucket,
string DestinationKey);
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
internal record DeletePlayListFromDbArguments(
Guid PlayListId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
internal record DeletePlayListFromDbArguments(
Guid PlayListId);
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
internal record DeleteTrackFromDbArguments(
Guid TrackId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
internal record DeleteTrackFromDbArguments(
Guid TrackId);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record DownloadFileFromBucketArguments(
string Bucket,
string Key,
string DestinationFilePathVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record DownloadFileFromBucketArguments(
string Bucket,
string Key,
string DestinationFilePathVariable);
@@ -1,6 +1,5 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record DownloadFileFromUrlArguments(
string SourceUrl,
string DestinationFilePathVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record DownloadFileFromUrlArguments(
string SourceUrl,
string DestinationFilePathVariable);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record GenerateAudioWorkflowPathsArguments(
Guid TrackId,
string TemporaryRootDirectory,
string SourceKey);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record GenerateAudioWorkflowPathsArguments(
Guid TrackId,
string TemporaryRootDirectory,
string SourceKey);
@@ -1,6 +1,5 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record GeneratePictureWorkflowPathsArguments(
string TemporaryRootDirectory,
string SourceKey);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record GeneratePictureWorkflowPathsArguments(
string TemporaryRootDirectory,
string SourceKey);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record MarkAlbumAsFailedArguments(Guid AlbumId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record MarkAlbumAsFailedArguments(Guid AlbumId);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record MarkPlayListAsFailedArguments(Guid PlayListId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record MarkPlayListAsFailedArguments(Guid PlayListId);
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
internal record MarkPlayListAsRemovingArguments(
Guid PlayListId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
internal record MarkPlayListAsRemovingArguments(
Guid PlayListId);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record MarkTrackAsFailedArguments(Guid TrackId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record MarkTrackAsFailedArguments(Guid TrackId);
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
internal record MarkTrackAsRemovingArguments(
Guid TrackId);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
internal record MarkTrackAsRemovingArguments(
Guid TrackId);
@@ -1,12 +1,11 @@
using Musify.Application.Shared;
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record PublishAlbumPictureProcessingEventArguments(
Guid AlbumId,
string Bucket,
string FinalPictureKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record PublishAlbumPictureProcessingEventArguments(
Guid AlbumId,
string Bucket,
string FinalPictureKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
@@ -1,12 +1,11 @@
using Musify.Application.Shared;
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record PublishPlayListPictureProcessingEventArguments(
Guid PlayListId,
string Bucket,
string FinalPictureKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record PublishPlayListPictureProcessingEventArguments(
Guid PlayListId,
string Bucket,
string FinalPictureKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
@@ -1,14 +1,13 @@
using Musify.Application.Shared;
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record PublishTrackProcessingEventsArguments(
Guid TrackId,
string Bucket,
string FinalPictureKey,
string FinalAudioKey,
string AudioDestinationFolderKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record PublishTrackProcessingEventsArguments(
Guid TrackId,
string Bucket,
string FinalPictureKey,
string FinalAudioKey,
string AudioDestinationFolderKey,
ImageSize Small,
ImageSize Medium,
ImageSize Large);
@@ -1,6 +1,5 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record RemoveFileFromBucketArguments(
string Bucket,
string Key);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record RemoveFileFromBucketArguments(
string Bucket,
string Key);
@@ -1,8 +1,7 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record ResizePictureLocalArguments(
string SourceFilePathVariable,
string DestinationFilePathVariable,
int Width,
int Height);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record ResizePictureLocalArguments(
string SourceFilePathVariable,
string DestinationFilePathVariable,
int Width,
int Height);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record TranscodeAudioArguments(
Guid TrackId,
string SourceFilePathVariable,
string WorkingDirectoryVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record TranscodeAudioArguments(
Guid TrackId,
string SourceFilePathVariable,
string WorkingDirectoryVariable);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record TransferFilesToBucketArguments(
string DestinationBucket,
string SourceDirectoryVariable,
string DestinationKey);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record TransferFilesToBucketArguments(
string DestinationBucket,
string SourceDirectoryVariable,
string DestinationKey);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record UpdateAlbumPictureArguments(
Guid AlbumId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record UpdateAlbumPictureArguments(
Guid AlbumId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record UpdatePlayListPictureArguments(
Guid PlayListId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record UpdatePlayListPictureArguments(
Guid PlayListId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record UpdateTrackAudioArguments(
Guid TrackId,
string AudioFolderKey,
string DurationSecondsVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record UpdateTrackAudioArguments(
Guid TrackId,
string AudioFolderKey,
string DurationSecondsVariable);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record UpdateTrackPictureArguments(
Guid TrackId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record UpdateTrackPictureArguments(
Guid TrackId,
string OriginalPictureKey,
string SmallPictureVariable,
string MediumPictureVariable,
string LargePictureVariable);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Arguments
{
public record UploadFileToBucketArguments(
string FilePathVariable,
string DestinationBucket,
string DestinationRoute);
}
namespace Musify.Infrastructure.MassTransit.Arguments;
public record UploadFileToBucketArguments(
string FilePathVariable,
string DestinationBucket,
string DestinationRoute);
@@ -6,31 +6,32 @@ using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.RoutingSlip;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class AlbumProcessingFailedConsumer(IBus bus) : IConsumer<AlbumProcessingFailed>
{
public class AlbumProcessingFailedConsumer(IBus bus) : IConsumer<AlbumProcessingFailed>
public async Task Consume(ConsumeContext<AlbumProcessingFailed> context)
{
public async Task Consume(ConsumeContext<AlbumProcessingFailed> context)
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
{
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
builder.AddActivity(
ActivityNames.RemoveAlbumOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
builder.AddActivity(
ActivityNames.MarkAlbumAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkAlbumAsFailedActivity.ExecuteEndpointName),
new MarkAlbumAsFailedArguments(message.AlbumId));
await bus.Execute(builder.Build());
ActivityNames.RemoveAlbumOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
}
builder.AddActivity(
ActivityNames.MarkAlbumAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkAlbumAsFailedActivity.ExecuteEndpointName),
new MarkAlbumAsFailedArguments(message.AlbumId));
await bus.Execute(builder.Build());
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class CreateAlbumConsumer(
IBus bus,
AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreateAlbumResourcesEvent>
{
public class CreateAlbumConsumer(
IBus bus,
AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreateAlbumResourcesEvent>
public async Task Consume(ConsumeContext<CreateAlbumResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<CreateAlbumResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class CreatePlayListConsumer(
IBus bus,
PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreatePlayListResourcesEvent>
{
public class CreatePlayListConsumer(
IBus bus,
PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreatePlayListResourcesEvent>
public async Task Consume(ConsumeContext<CreatePlayListResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<CreatePlayListResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class CreateTrackConsumer(
IBus bus,
CreateTrackRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreateTrackResourcesEvent>
{
public class CreateTrackConsumer(
IBus bus,
CreateTrackRoutingSlipBuilder routingSlipBuilder)
: IConsumer<CreateTrackResourcesEvent>
public async Task Consume(ConsumeContext<CreateTrackResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<CreateTrackResourcesEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -2,25 +2,23 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class DeletePlayListConsumer(
IBus bus,
DeletePlayListRoutingSlipBuilder routingSlipBuilder)
: IConsumer<DeletePlayListEvent>
{
public class DeletePlayListConsumer(
IBus bus,
DeletePlayListRoutingSlipBuilder routingSlipBuilder)
: IConsumer<DeletePlayListEvent>
public async Task Consume(ConsumeContext<DeletePlayListEvent> consumeContext)
{
var routingSlip = (await routingSlipBuilder
.BuildAsync(
consumeContext.Message.PlayListId,
consumeContext.Message.UserId,
consumeContext.CorrelationId,
consumeContext.CancellationToken))
.Build();
public async Task Consume(ConsumeContext<DeletePlayListEvent> consumeContext)
{
var routingSlip = (await routingSlipBuilder
.BuildAsync(
consumeContext.Message.PlayListId,
consumeContext.Message.UserId,
consumeContext.CorrelationId,
consumeContext.CancellationToken))
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class DeleteTrackConsumer(
IBus bus,
DeleteTrackRoutingSlipBuilder routingSlipBuilder)
: IConsumer<DeleteTrackEvent>
{
public class DeleteTrackConsumer(
IBus bus,
DeleteTrackRoutingSlipBuilder routingSlipBuilder)
: IConsumer<DeleteTrackEvent>
public async Task Consume(ConsumeContext<DeleteTrackEvent> consumeContext)
{
var routingSlip = (await routingSlipBuilder
.BuildAsync(consumeContext.Message.TrackId, consumeContext.Message.UserId, consumeContext.CorrelationId, consumeContext.CancellationToken))
.Build();
public async Task Consume(ConsumeContext<DeleteTrackEvent> consumeContext)
{
var routingSlip = (await routingSlipBuilder
.BuildAsync(consumeContext.Message.TrackId, consumeContext.Message.UserId, consumeContext.CorrelationId, consumeContext.CancellationToken))
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Consumers
{
internal static class EndpointHelper
{
internal static Uri BuildExecuteActivityUri(string endpointName) => new($"queue:{endpointName}_execute");
namespace Musify.Infrastructure.MassTransit.Consumers;
internal static Uri BuildConsumerUri(string endpointName) => new($"queue:{endpointName}");
}
internal static class EndpointHelper
{
internal static Uri BuildExecuteActivityUri(string endpointName) => new($"queue:{endpointName}_execute");
internal static Uri BuildConsumerUri(string endpointName) => new($"queue:{endpointName}");
}
@@ -6,31 +6,32 @@ using Musify.Infrastructure.MassTransit.Activities.PlayLists;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.RoutingSlip;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class PlayListProcessingFailedConsumer(IBus bus) : IConsumer<PlayListProcessingFailed>
{
public class PlayListProcessingFailedConsumer(IBus bus) : IConsumer<PlayListProcessingFailed>
public async Task Consume(ConsumeContext<PlayListProcessingFailed> context)
{
public async Task Consume(ConsumeContext<PlayListProcessingFailed> context)
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
{
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
builder.AddActivity(
ActivityNames.RemovePlayListOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
builder.AddActivity(
ActivityNames.MarkPlayListAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsFailedActivity.ExecuteEndpointName),
new MarkPlayListAsFailedArguments(message.PlayListId));
await bus.Execute(builder.Build());
ActivityNames.RemovePlayListOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
}
builder.AddActivity(
ActivityNames.MarkPlayListAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsFailedActivity.ExecuteEndpointName),
new MarkPlayListAsFailedArguments(message.PlayListId));
await bus.Execute(builder.Build());
}
}
@@ -2,35 +2,36 @@ using MassTransit;
using MassTransit.Courier.Contracts;
using Musify.Application.Events;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class ProcessingSlipFaultConsumer : IConsumer<RoutingSlipFaulted>
{
public class ProcessingSlipFaultConsumer : IConsumer<RoutingSlipFaulted>
public const string QueueName = "processing-slip-fault";
public async Task Consume(ConsumeContext<RoutingSlipFaulted> context)
{
public const string QueueName = "processing-slip-fault";
var variables = context.Message.Variables;
public async Task Consume(ConsumeContext<RoutingSlipFaulted> context)
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.ProcessKind, out var kindObject))
return;
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.SubjectId, out var subjectObject)
|| !Guid.TryParse(subjectObject?.ToString(), out var subjectId))
{
var variables = context.Message.Variables;
return;
}
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.ProcessKind, out var kindObject))
return;
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.SubjectId, out var subjectObject)
|| !Guid.TryParse(subjectObject?.ToString(), out var subjectId))
return;
switch (kindObject?.ToString())
{
case RoutingSlipVariableNames.ProcessKinds.TrackPicture:
await context.Publish(new TrackPictureProcessingFailed(subjectId));
break;
case RoutingSlipVariableNames.ProcessKinds.TrackAudio:
await context.Publish(new TrackAudioProcessingFailed(subjectId));
break;
case RoutingSlipVariableNames.ProcessKinds.PlayListPicture:
await context.Publish(new PlayListPictureProcessingFailed(subjectId));
break;
}
switch (kindObject?.ToString())
{
case RoutingSlipVariableNames.ProcessKinds.TrackPicture:
await context.Publish(new TrackPictureProcessingFailed(subjectId));
break;
case RoutingSlipVariableNames.ProcessKinds.TrackAudio:
await context.Publish(new TrackAudioProcessingFailed(subjectId));
break;
case RoutingSlipVariableNames.ProcessKinds.PlayListPicture:
await context.Publish(new PlayListPictureProcessingFailed(subjectId));
break;
}
}
}
@@ -1,48 +1,47 @@
using MassTransit;
using MassTransit;
using MassTransit.Courier.Contracts;
using Microsoft.Extensions.Logging;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class RoutingSlipCleanUpConsumer(
ILogger<RoutingSlipCleanUpConsumer> logger) :
IConsumer<RoutingSlipCompleted>,
IConsumer<RoutingSlipFaulted>
{
public class RoutingSlipCleanUpConsumer(
ILogger<RoutingSlipCleanUpConsumer> logger) :
IConsumer<RoutingSlipCompleted>,
IConsumer<RoutingSlipFaulted>
public const string QueueName = "routing-slip-clean-up";
public Task Consume(ConsumeContext<RoutingSlipCompleted> consumeContext)
=> DeleteFolder(consumeContext.Message.Variables);
public Task Consume(ConsumeContext<RoutingSlipFaulted> consumeContext)
=> DeleteFolder(consumeContext.Message.Variables);
private Task DeleteFolder(IDictionary<string, object> variables)
{
public const string QueueName = "routing-slip-clean-up";
public Task Consume(ConsumeContext<RoutingSlipCompleted> consumeContext)
=> DeleteFolder(consumeContext.Message.Variables);
public Task Consume(ConsumeContext<RoutingSlipFaulted> consumeContext)
=> DeleteFolder(consumeContext.Message.Variables);
Task DeleteFolder(IDictionary<string, object> variables)
{
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.TemporalDirectory, out var pathObject))
return Task.CompletedTask;
var pathString = pathObject.ToString();
if (string.IsNullOrEmpty(pathString))
return Task.CompletedTask;
try
{
Directory.Delete(pathString, recursive: true);
logger.LogInformation("Deleted temporal directory at path {Path}", pathString);
}
catch (DirectoryNotFoundException directoryNotFoundException)
{
logger.LogWarning(directoryNotFoundException, "Temporal directory at path {Path} was not found for deletion", pathString);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to delete temporal directory at path {Path}", pathString);
throw;
}
if (!variables.TryGetValue(RoutingSlipVariableNames.Workflow.TemporalDirectory, out var pathObject))
return Task.CompletedTask;
var pathString = pathObject.ToString();
if (string.IsNullOrEmpty(pathString))
return Task.CompletedTask;
try
{
Directory.Delete(pathString, recursive: true);
logger.LogInformation("Deleted temporal directory at path {Path}", pathString);
}
catch (DirectoryNotFoundException directoryNotFoundException)
{
logger.LogWarning(directoryNotFoundException, "Temporal directory at path {Path} was not found for deletion", pathString);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to delete temporal directory at path {Path}", pathString);
throw;
}
return Task.CompletedTask;
}
}
}
@@ -6,37 +6,40 @@ using Musify.Infrastructure.MassTransit.Activities.Tracks;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.RoutingSlip;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class TrackProcessingFailedConsumer(IBus bus) : IConsumer<TrackProcessingFailed>
{
public class TrackProcessingFailedConsumer(IBus bus) : IConsumer<TrackProcessingFailed>
public async Task Consume(ConsumeContext<TrackProcessingFailed> context)
{
public async Task Consume(ConsumeContext<TrackProcessingFailed> context)
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
{
var message = context.Message;
var builder = new RoutingSlipBuilder(NewId.NextGuid());
builder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.PictureKey))
builder.AddActivity(
ActivityNames.RemoveTrackOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.AudioKey))
builder.AddActivity(
ActivityNames.RemoveTrackOriginalAudio,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.AudioKey));
builder.AddActivity(
ActivityNames.MarkTrackAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkTrackAsFailedActivity.ExecuteEndpointName),
new MarkTrackAsFailedArguments(message.TrackId));
await bus.Execute(builder.Build());
ActivityNames.RemoveTrackOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.PictureKey));
}
if (!string.IsNullOrWhiteSpace(message.Bucket) && !string.IsNullOrWhiteSpace(message.AudioKey))
{
builder.AddActivity(
ActivityNames.RemoveTrackOriginalAudio,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(message.Bucket, message.AudioKey));
}
builder.AddActivity(
ActivityNames.MarkTrackAsFailed,
EndpointHelper.BuildExecuteActivityUri(MarkTrackAsFailedActivity.ExecuteEndpointName),
new MarkTrackAsFailedArguments(message.TrackId));
await bus.Execute(builder.Build());
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdateAlbumPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateAlbumPictureEvent>
{
public class UpdateAlbumPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateAlbumPictureEvent>
public async Task Consume(ConsumeContext<UpdateAlbumPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdateAlbumPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdateAlbumPictureSourceConsumer(
IBus bus,
AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateAlbumPictureSourceEvent>
{
public class UpdateAlbumPictureSourceConsumer(
IBus bus,
AlbumPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateAlbumPictureSourceEvent>
public async Task Consume(ConsumeContext<UpdateAlbumPictureSourceEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdateAlbumPictureSourceEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -1,22 +1,20 @@
using MassTransit;
using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdatePlayListPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdatePlayListPictureEvent>
{
public class UpdatePlayListPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdatePlayListPictureEvent>
public async Task Consume(ConsumeContext<UpdatePlayListPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdatePlayListPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
}
@@ -2,21 +2,19 @@ using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdatePlayListPictureSourceConsumer(
IBus bus,
PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdatePlayListPictureSourceEvent>
{
public class UpdatePlayListPictureSourceConsumer(
IBus bus,
PlayListPictureSourceRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdatePlayListPictureSourceEvent>
public async Task Consume(ConsumeContext<UpdatePlayListPictureSourceEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdatePlayListPictureSourceEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
@@ -1,22 +1,20 @@
using MassTransit;
using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdateTrackAudioConsumer(
IBus bus,
AudioWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateTrackAudioEvent>
{
public class UpdateTrackAudioConsumer(
IBus bus,
AudioWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateTrackAudioEvent>
public async Task Consume(ConsumeContext<UpdateTrackAudioEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdateTrackAudioEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
}
@@ -1,22 +1,20 @@
using MassTransit;
using MassTransit;
using Musify.Application.Events;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Consumers
namespace Musify.Infrastructure.MassTransit.Consumers;
public class UpdateTrackPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateTrackPictureEvent>
{
public class UpdateTrackPictureConsumer(
IBus bus,
PictureWorkflowRoutingSlipBuilder routingSlipBuilder)
: IConsumer<UpdateTrackPictureEvent>
public async Task Consume(ConsumeContext<UpdateTrackPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
public async Task Consume(ConsumeContext<UpdateTrackPictureEvent> consumeContext)
{
var routingSlip = routingSlipBuilder
.Build(consumeContext.Message, consumeContext.CorrelationId)
.Build();
await bus.Execute(routingSlip);
}
await bus.Execute(routingSlip);
}
}
}
@@ -2,23 +2,22 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Musify.Infrastructure.MassTransit
{
public static partial class MassTransitDependencyInjection
{
static void RegisterValidatedOptions<TOptions>(
IServiceCollection services,
IConfiguration configuration,
string sectionName)
where TOptions : class
{
services
.AddOptionsWithValidateOnStart<TOptions>()
.Bind(configuration.GetRequiredSection(sectionName))
.ValidateDataAnnotations();
namespace Musify.Infrastructure.MassTransit;
services.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<TOptions>>().Value);
}
public static partial class MassTransitDependencyInjection
{
private static void RegisterValidatedOptions<TOptions>(
IServiceCollection services,
IConfiguration configuration,
string sectionName)
where TOptions : class
{
services
.AddOptionsWithValidateOnStart<TOptions>()
.Bind(configuration.GetRequiredSection(sectionName))
.ValidateDataAnnotations();
services.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<TOptions>>().Value);
}
}
@@ -1,9 +1,9 @@
using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Musify.Infrastructure.MassTransit.Activities.Pictures;
using Musify.Infrastructure.MassTransit.Activities.Albums;
using Musify.Infrastructure.MassTransit.Activities.Audio;
using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.Pictures;
using Musify.Infrastructure.MassTransit.Activities.PlayLists;
using Musify.Infrastructure.MassTransit.Activities.Tracks;
using Musify.Infrastructure.MassTransit.Activities.UploadIntents;
@@ -12,66 +12,65 @@ using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Infrastructure.MassTransit.Logs;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit
namespace Musify.Infrastructure.MassTransit;
public static partial class MassTransitDependencyInjection
{
public static partial class MassTransitDependencyInjection
private static void RegisterRoutingSlipBuilders(IServiceCollection services)
{
static void RegisterRoutingSlipBuilders(IServiceCollection services)
{
services.AddScoped<AudioWorkflowRoutingSlipBuilder>();
services.AddScoped<PictureWorkflowRoutingSlipBuilder>();
services.AddScoped<DeleteTrackRoutingSlipBuilder>();
services.AddScoped<DeletePlayListRoutingSlipBuilder>();
services.AddScoped<CreateTrackRoutingSlipBuilder>();
services.AddScoped<PlayListPictureSourceRoutingSlipBuilder>();
services.AddScoped<AlbumPictureSourceRoutingSlipBuilder>();
}
services.AddScoped<AudioWorkflowRoutingSlipBuilder>();
services.AddScoped<PictureWorkflowRoutingSlipBuilder>();
services.AddScoped<DeleteTrackRoutingSlipBuilder>();
services.AddScoped<DeletePlayListRoutingSlipBuilder>();
services.AddScoped<CreateTrackRoutingSlipBuilder>();
services.AddScoped<PlayListPictureSourceRoutingSlipBuilder>();
services.AddScoped<AlbumPictureSourceRoutingSlipBuilder>();
}
static void RegisterConsumersAndActivities(IBusRegistrationConfigurator options)
{
options.AddConsumer<UpdatePlayListPictureConsumer>();
options.AddConsumer<UpdateTrackPictureConsumer>();
options.AddConsumer<RoutingSlipCleanUpConsumer>();
options.AddConsumer<UpdateTrackAudioConsumer>();
options.AddConsumer<DeleteTrackConsumer>();
options.AddConsumer<DeletePlayListConsumer>();
options.AddConsumer<CreateTrackConsumer>();
options.AddConsumer<CreatePlayListConsumer>();
options.AddConsumer<UpdatePlayListPictureSourceConsumer>();
options.AddConsumer<ProcessingSlipFaultConsumer>();
options.AddConsumer<TrackProcessingFailedConsumer>();
options.AddConsumer<PlayListProcessingFailedConsumer>();
options.AddConsumer<CreateAlbumConsumer>();
options.AddConsumer<UpdateAlbumPictureSourceConsumer>();
options.AddConsumer<UpdateAlbumPictureConsumer>();
options.AddConsumer<AlbumProcessingFailedConsumer>();
private static void RegisterConsumersAndActivities(IBusRegistrationConfigurator options)
{
options.AddConsumer<UpdatePlayListPictureConsumer>();
options.AddConsumer<UpdateTrackPictureConsumer>();
options.AddConsumer<RoutingSlipCleanUpConsumer>();
options.AddConsumer<UpdateTrackAudioConsumer>();
options.AddConsumer<DeleteTrackConsumer>();
options.AddConsumer<DeletePlayListConsumer>();
options.AddConsumer<CreateTrackConsumer>();
options.AddConsumer<CreatePlayListConsumer>();
options.AddConsumer<UpdatePlayListPictureSourceConsumer>();
options.AddConsumer<ProcessingSlipFaultConsumer>();
options.AddConsumer<TrackProcessingFailedConsumer>();
options.AddConsumer<PlayListProcessingFailedConsumer>();
options.AddConsumer<CreateAlbumConsumer>();
options.AddConsumer<UpdateAlbumPictureSourceConsumer>();
options.AddConsumer<UpdateAlbumPictureConsumer>();
options.AddConsumer<AlbumProcessingFailedConsumer>();
options.AddExecuteActivity<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>();
options.AddExecuteActivity<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>();
options.AddExecuteActivity<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>();
options.AddExecuteActivity<MarkTrackAsRemovingActivity, MarkTrackAsRemovingArguments>();
options.AddExecuteActivity<DeleteTrackFromDbActivity, DeleteTrackFromDbArguments>();
options.AddExecuteActivity<MarkPlayListAsRemovingActivity, MarkPlayListAsRemovingArguments>();
options.AddExecuteActivity<DeletePlayListFromDbActivity, DeletePlayListFromDbArguments>();
options.AddExecuteActivity<PublishTrackProcessingEventsActivity, PublishTrackProcessingEventsArguments>();
options.AddExecuteActivity<PublishPlayListPictureProcessingEventActivity, PublishPlayListPictureProcessingEventArguments>();
options.AddExecuteActivity<MarkTrackAsFailedActivity, MarkTrackAsFailedArguments>();
options.AddExecuteActivity<MarkPlayListAsFailedActivity, MarkPlayListAsFailedArguments>();
options.AddExecuteActivity<MarkAlbumAsFailedActivity, MarkAlbumAsFailedArguments>();
options.AddExecuteActivity<PublishAlbumPictureProcessingEventActivity, PublishAlbumPictureProcessingEventArguments>();
options.AddExecuteActivity<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>();
options.AddExecuteActivity<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>();
options.AddExecuteActivity<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>();
options.AddExecuteActivity<MarkTrackAsRemovingActivity, MarkTrackAsRemovingArguments>();
options.AddExecuteActivity<DeleteTrackFromDbActivity, DeleteTrackFromDbArguments>();
options.AddExecuteActivity<MarkPlayListAsRemovingActivity, MarkPlayListAsRemovingArguments>();
options.AddExecuteActivity<DeletePlayListFromDbActivity, DeletePlayListFromDbArguments>();
options.AddExecuteActivity<PublishTrackProcessingEventsActivity, PublishTrackProcessingEventsArguments>();
options.AddExecuteActivity<PublishPlayListPictureProcessingEventActivity, PublishPlayListPictureProcessingEventArguments>();
options.AddExecuteActivity<MarkTrackAsFailedActivity, MarkTrackAsFailedArguments>();
options.AddExecuteActivity<MarkPlayListAsFailedActivity, MarkPlayListAsFailedArguments>();
options.AddExecuteActivity<MarkAlbumAsFailedActivity, MarkAlbumAsFailedArguments>();
options.AddExecuteActivity<PublishAlbumPictureProcessingEventActivity, PublishAlbumPictureProcessingEventArguments>();
options.AddActivity<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>();
options.AddActivity<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>();
options.AddActivity<UpdateAlbumPictureActivity, UpdateAlbumPictureArguments, UpdateAlbumPictureLog>();
options.AddActivity<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>();
options.AddActivity<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>();
options.AddActivity<TranscodeAudioActivity, TranscodeAudioArguments, TranscodeAudioLog>();
options.AddActivity<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>();
options.AddActivity<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>();
options.AddActivity<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>();
options.AddActivity<CopyFileInBucketActivity, CopyFileInBucketArguments, CopyFileInBucketLog>();
options.AddActivity<ConsumeUploadIntentsActivity, ConsumeUploadIntentsArguments, ConsumeUploadIntentsLog>();
options.AddActivity<DownloadFileFromUrlActivity, DownloadFileFromUrlArguments, DownloadFileFromUrlLog>();
}
options.AddActivity<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>();
options.AddActivity<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>();
options.AddActivity<UpdateAlbumPictureActivity, UpdateAlbumPictureArguments, UpdateAlbumPictureLog>();
options.AddActivity<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>();
options.AddActivity<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>();
options.AddActivity<TranscodeAudioActivity, TranscodeAudioArguments, TranscodeAudioLog>();
options.AddActivity<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>();
options.AddActivity<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>();
options.AddActivity<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>();
options.AddActivity<CopyFileInBucketActivity, CopyFileInBucketArguments, CopyFileInBucketLog>();
options.AddActivity<ConsumeUploadIntentsActivity, ConsumeUploadIntentsArguments, ConsumeUploadIntentsLog>();
options.AddActivity<DownloadFileFromUrlActivity, DownloadFileFromUrlArguments, DownloadFileFromUrlLog>();
}
}
@@ -6,127 +6,126 @@ using Musify.Infrastructure.Configuration;
using Musify.Infrastructure.MassTransit.Sagas;
using Musify.Infrastructure.Persistence;
namespace Musify.Infrastructure.MassTransit
namespace Musify.Infrastructure.MassTransit;
public static partial class MassTransitDependencyInjection
{
public static partial class MassTransitDependencyInjection
public static IServiceCollection AddMassTransitClient(this IServiceCollection serviceDescriptors, IConfiguration configuration)
{
public static IServiceCollection AddMassTransitClient(this IServiceCollection serviceDescriptors, IConfiguration configuration)
RegisterValidatedOptions<MassTransitConfiguration>(
serviceDescriptors,
configuration,
MassTransitConfiguration.SectionName);
serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>();
serviceDescriptors.AddMassTransit(options =>
{
RegisterValidatedOptions<MassTransitConfiguration>(
serviceDescriptors,
configuration,
MassTransitConfiguration.SectionName);
serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>();
serviceDescriptors.AddMassTransit(options =>
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
} );
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
});
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>()));
} );
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>()));
});
return serviceDescriptors;
}
public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors, IConfiguration configuration)
{
RegisterValidatedOptions<MassTransitConfiguration>(
serviceDescriptors,
configuration,
MassTransitConfiguration.SectionName);
RegisterValidatedOptions<WorkerConfiguration>(
serviceDescriptors,
configuration,
WorkerConfiguration.SectionName);
RegisterRoutingSlipBuilders(serviceDescriptors);
serviceDescriptors.AddMassTransit(options =>
{
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
} );
options.SetKebabCaseEndpointNameFormatter();
RegisterConsumersAndActivities(options);
options.AddSagaStateMachine<TrackProcessingStateMachine, TrackProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
} );
options.AddSagaStateMachine<PlayListProcessingStateMachine, PlayListProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
} );
options.AddSagaStateMachine<AlbumProcessingStateMachine, AlbumProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
} );
options.AddConfigureEndpointsCallback((registrationContext, _, endpointConfigurator) =>
{
UseStandardRetry(endpointConfigurator);
endpointConfigurator.UseEntityFrameworkOutbox<Database>(registrationContext);
} );
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
{
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>());
busFactoryConfigurator.ConfigureEndpoints(busRegistrationContext);
} );
} );
return serviceDescriptors;
}
static void UseStandardRetry(IReceiveEndpointConfigurator endpointConfigurator) =>
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
static void ConfigureRabbitMqHost(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
MassTransitConfiguration massTransitConfiguration)
{
busFactoryConfigurator.Host(massTransitConfiguration.Address, options =>
{
options.Username(massTransitConfiguration.Username);
options.Password(massTransitConfiguration.Password);
} );
}
return serviceDescriptors;
}
}
public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors, IConfiguration configuration)
{
RegisterValidatedOptions<MassTransitConfiguration>(
serviceDescriptors,
configuration,
MassTransitConfiguration.SectionName);
RegisterValidatedOptions<WorkerConfiguration>(
serviceDescriptors,
configuration,
WorkerConfiguration.SectionName);
RegisterRoutingSlipBuilders(serviceDescriptors);
serviceDescriptors.AddMassTransit(options =>
{
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
});
options.SetKebabCaseEndpointNameFormatter();
RegisterConsumersAndActivities(options);
options.AddSagaStateMachine<TrackProcessingStateMachine, TrackProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
});
options.AddSagaStateMachine<PlayListProcessingStateMachine, PlayListProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
});
options.AddSagaStateMachine<AlbumProcessingStateMachine, AlbumProcessingState>()
.EntityFrameworkRepository(repository =>
{
repository.ConcurrencyMode = ConcurrencyMode.Pessimistic;
repository.ExistingDbContext<Database>();
repository.UsePostgres();
});
options.AddConfigureEndpointsCallback((registrationContext, _, endpointConfigurator) =>
{
UseStandardRetry(endpointConfigurator);
endpointConfigurator.UseEntityFrameworkOutbox<Database>(registrationContext);
});
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
{
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>());
busFactoryConfigurator.ConfigureEndpoints(busRegistrationContext);
});
});
return serviceDescriptors;
}
private static void UseStandardRetry(IReceiveEndpointConfigurator endpointConfigurator) =>
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
private static void ConfigureRabbitMqHost(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
MassTransitConfiguration massTransitConfiguration)
{
busFactoryConfigurator.Host(massTransitConfiguration.Address, options =>
{
options.Username(massTransitConfiguration.Username);
options.Password(massTransitConfiguration.Password);
});
}
}
@@ -1,5 +1,4 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record ConsumeUploadIntentsLog(
Guid[] IntentIds);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record ConsumeUploadIntentsLog(
Guid[] IntentIds);
@@ -1,6 +1,5 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record CopyFileInBucketLog(
string DestinationBucket,
string DestinationKey);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record CopyFileInBucketLog(
string DestinationBucket,
string DestinationKey);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record DownloadFileFromBucketLog(
string Bucket,
string Key,
string DestinationFilePath);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record DownloadFileFromBucketLog(
string Bucket,
string Key,
string DestinationFilePath);
@@ -1,6 +1,5 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record DownloadFileFromUrlLog(
string SourceUrl,
string DestinationFilePath);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record DownloadFileFromUrlLog(
string SourceUrl,
string DestinationFilePath);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record GeneratePictureWorkflowPathsLog(string WorkingDirectory);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record GeneratePictureWorkflowPathsLog(string WorkingDirectory);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record ResizePictureLog(string DestinationFilePath);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record ResizePictureLog(string DestinationFilePath);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record TranscodeAudioLog(string WorkingDirectory);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record TranscodeAudioLog(string WorkingDirectory);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record TransferFilesToBucketLog(string DestinationBucket, string[] UploadedKeys);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record TransferFilesToBucketLog(string DestinationBucket, string[] UploadedKeys);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record UpdateAlbumPictureLog(
Guid AlbumId,
string PreviousOriginalPictureKey,
string PreviousSmallPictureKey,
string PreviousMediumPictureKey,
string PreviousLargePictureKey);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record UpdateAlbumPictureLog(
Guid AlbumId,
string PreviousOriginalPictureKey,
string PreviousSmallPictureKey,
string PreviousMediumPictureKey,
string PreviousLargePictureKey);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record UpdatePlayListPictureLog(
Guid PlayListId,
string PreviousOriginalPictureKey,
string PreviousSmallPictureKey,
string PreviousMediumPictureKey,
string PreviousLargePictureKey);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record UpdatePlayListPictureLog(
Guid PlayListId,
string PreviousOriginalPictureKey,
string PreviousSmallPictureKey,
string PreviousMediumPictureKey,
string PreviousLargePictureKey);
@@ -1,7 +1,6 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record UpdateTrackAudioLog(
Guid TrackId,
string? PreviousAudioFolderName,
double PreviousDurationSeconds);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record UpdateTrackAudioLog(
Guid TrackId,
string? PreviousAudioFolderName,
double PreviousDurationSeconds);
@@ -1,9 +1,8 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record UpdateTrackPictureLog(
Guid TrackId,
string? PreviousOriginalPictureKey,
string? PreviousSmallPictureKey,
string? PreviousMediumPictureKey,
string? PreviousLargePictureKey);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record UpdateTrackPictureLog(
Guid TrackId,
string? PreviousOriginalPictureKey,
string? PreviousSmallPictureKey,
string? PreviousMediumPictureKey,
string? PreviousLargePictureKey);
@@ -1,4 +1,3 @@
namespace Musify.Infrastructure.MassTransit.Logs
{
public record UploadFileToBucketLog(string DestinationBucket, string DestinationKey);
}
namespace Musify.Infrastructure.MassTransit.Logs;
public record UploadFileToBucketLog(string DestinationBucket, string DestinationKey);
@@ -1,16 +1,15 @@
using MassTransit;
using Musify.Application.Contracts;
namespace Musify.Infrastructure.MassTransit
{
public class MassTransitEventBus(IPublishEndpoint publishEndpoint)
: IEventBus
{
public async Task PublishAsync<T>(T message, CancellationToken cancellationToken) where T : class
{
ArgumentNullException.ThrowIfNull(message, nameof(message));
namespace Musify.Infrastructure.MassTransit;
await publishEndpoint.Publish(message, cancellationToken);
}
public class MassTransitEventBus(IPublishEndpoint publishEndpoint)
: IEventBus
{
public async Task PublishAsync<T>(T message, CancellationToken cancellationToken) where T : class
{
ArgumentNullException.ThrowIfNull(message, nameof(message));
await publishEndpoint.Publish(message, cancellationToken);
}
}
}
@@ -1,59 +1,58 @@
namespace Musify.Infrastructure.MassTransit.RoutingSlip
namespace Musify.Infrastructure.MassTransit.RoutingSlip;
internal static class ActivityNames
{
internal static class ActivityNames
{
internal const string MarkTrackAsRemoving = "MarkTrackAsRemoving";
internal const string MarkTrackAsFailed = "MarkTrackAsFailed";
internal const string DeleteTrackFromDb = "DeleteTrackFromDb";
internal const string MarkPlayListAsRemoving = "MarkPlayListAsRemoving";
internal const string MarkPlayListAsFailed = "MarkPlayListAsFailed";
internal const string DeletePlayListFromDb = "DeletePlayListFromDb";
internal const string MarkAlbumAsFailed = "MarkAlbumAsFailed";
internal const string MarkTrackAsRemoving = "MarkTrackAsRemoving";
internal const string MarkTrackAsFailed = "MarkTrackAsFailed";
internal const string DeleteTrackFromDb = "DeleteTrackFromDb";
internal const string MarkPlayListAsRemoving = "MarkPlayListAsRemoving";
internal const string MarkPlayListAsFailed = "MarkPlayListAsFailed";
internal const string DeletePlayListFromDb = "DeletePlayListFromDb";
internal const string MarkAlbumAsFailed = "MarkAlbumAsFailed";
internal const string GenerateAudioWorkflowPaths = "GenerateAudioWorkflowPaths";
internal const string GeneratePictureWorkflowPaths = "GeneratePictureWorkflowPaths";
internal const string GenerateAudioWorkflowPaths = "GenerateAudioWorkflowPaths";
internal const string GeneratePictureWorkflowPaths = "GeneratePictureWorkflowPaths";
internal const string DownloadFile = "DownloadFile";
internal const string DownloadThumbnail = "DownloadThumbnail";
internal const string DownloadFile = "DownloadFile";
internal const string DownloadThumbnail = "DownloadThumbnail";
internal const string TranscodeAudio = "TranscodeAudio";
internal const string TranscodeAudio = "TranscodeAudio";
internal const string ResizeSmall = "ResizeSmall";
internal const string ResizeMedium = "ResizeMedium";
internal const string ResizeLarge = "ResizeLarge";
internal const string ResizeSmall = "ResizeSmall";
internal const string ResizeMedium = "ResizeMedium";
internal const string ResizeLarge = "ResizeLarge";
internal const string UploadSmall = "UploadSmall";
internal const string UploadMedium = "UploadMedium";
internal const string UploadLarge = "UploadLarge";
internal const string UploadSmall = "UploadSmall";
internal const string UploadMedium = "UploadMedium";
internal const string UploadLarge = "UploadLarge";
internal const string TransferFiles = "TransferFiles";
internal const string TransferFiles = "TransferFiles";
internal const string UpdateTrackPicture = "UpdateTrackPicture";
internal const string UpdatePlayListPicture = "UpdatePlayListPicture";
internal const string UpdateAlbumPicture = "UpdateAlbumPicture";
internal const string UpdateTrackAudio = "UpdateTrackAudio";
internal const string UpdateTrackPicture = "UpdateTrackPicture";
internal const string UpdatePlayListPicture = "UpdatePlayListPicture";
internal const string UpdateAlbumPicture = "UpdateAlbumPicture";
internal const string UpdateTrackAudio = "UpdateTrackAudio";
internal const string CopyPictureToFinal = "CopyPictureToFinal";
internal const string CopyAudioToFinal = "CopyAudioToFinal";
internal const string CopyPictureToFinal = "CopyPictureToFinal";
internal const string CopyAudioToFinal = "CopyAudioToFinal";
internal const string ConsumeUploadIntents = "ConsumeUploadIntents";
internal const string ConsumeUploadIntents = "ConsumeUploadIntents";
internal const string PublishTrackProcessingEvents = "PublishTrackProcessingEvents";
internal const string PublishPlayListPictureProcessingEvent = "PublishPlayListPictureProcessingEvent";
internal const string PublishAlbumPictureProcessingEvent = "PublishAlbumPictureProcessingEvent";
internal const string PublishTrackProcessingEvents = "PublishTrackProcessingEvents";
internal const string PublishPlayListPictureProcessingEvent = "PublishPlayListPictureProcessingEvent";
internal const string PublishAlbumPictureProcessingEvent = "PublishAlbumPictureProcessingEvent";
internal const string RemoveTrackOriginalPicture = "RemoveTrackOriginalPicture";
internal const string RemoveTrackSmallPicture = "RemoveTrackSmallPicture";
internal const string RemoveTrackMediumPicture = "RemoveTrackMediumPicture";
internal const string RemoveTrackLargePicture = "RemoveTrackLargePicture";
internal const string RemoveTrackOriginalAudio = "RemoveTrackOriginalAudio";
internal const string RemoveTrackProcessedAudio = "RemoveTrackProcessedAudio";
internal const string RemoveTrackOriginalPicture = "RemoveTrackOriginalPicture";
internal const string RemoveTrackSmallPicture = "RemoveTrackSmallPicture";
internal const string RemoveTrackMediumPicture = "RemoveTrackMediumPicture";
internal const string RemoveTrackLargePicture = "RemoveTrackLargePicture";
internal const string RemoveTrackOriginalAudio = "RemoveTrackOriginalAudio";
internal const string RemoveTrackProcessedAudio = "RemoveTrackProcessedAudio";
internal const string RemovePlayListOriginalPicture = "RemovePlayListOriginalPicture";
internal const string RemovePlayListSmallPicture = "RemovePlayListSmallPicture";
internal const string RemovePlayListMediumPicture = "RemovePlayListMediumPicture";
internal const string RemovePlayListLargePicture = "RemovePlayListLargePicture";
internal const string RemovePlayListOriginalPicture = "RemovePlayListOriginalPicture";
internal const string RemovePlayListSmallPicture = "RemovePlayListSmallPicture";
internal const string RemovePlayListMediumPicture = "RemovePlayListMediumPicture";
internal const string RemovePlayListLargePicture = "RemovePlayListLargePicture";
internal const string RemoveAlbumOriginalPicture = "RemoveAlbumOriginalPicture";
}
internal const string RemoveAlbumOriginalPicture = "RemoveAlbumOriginalPicture";
}
@@ -1,85 +1,84 @@
using MassTransit;
using MassTransit.Courier.Contracts;
using Musify.Application.Shared;
using Musify.Application.Events;
using Musify.Application.Shared;
using Musify.Infrastructure.MassTransit.Activities.Albums;
using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.UploadIntents;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
public class AlbumPictureSourceRoutingSlipBuilder
{
public class AlbumPictureSourceRoutingSlipBuilder
public RoutingSlipBuilder Build(CreateAlbumResourcesEvent message, Guid? correlationId)
=> BuildSlip(
message.AlbumId,
message.PictureIntentId,
message.Bucket,
message.PictureSourceKey,
message.PictureDestinationKey,
message.Small,
message.Medium,
message.Large,
correlationId);
public RoutingSlipBuilder Build(UpdateAlbumPictureSourceEvent message, Guid? correlationId)
=> BuildSlip(
message.AlbumId,
message.PictureIntentId,
message.Bucket,
message.PictureSourceKey,
message.PictureDestinationKey,
message.Small,
message.Medium,
message.Large,
correlationId);
private static RoutingSlipBuilder BuildSlip(
Guid albumId,
Guid pictureIntentId,
string bucket,
string pictureSourceKey,
string pictureDestinationKey,
ImageSize small,
ImageSize medium,
ImageSize large,
Guid? correlationId)
{
public RoutingSlipBuilder Build(CreateAlbumResourcesEvent message, Guid? correlationId)
=> BuildSlip(
message.AlbumId,
message.PictureIntentId,
message.Bucket,
message.PictureSourceKey,
message.PictureDestinationKey,
message.Small,
message.Medium,
message.Large,
correlationId);
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
public RoutingSlipBuilder Build(UpdateAlbumPictureSourceEvent message, Guid? correlationId)
=> BuildSlip(
message.AlbumId,
message.PictureIntentId,
message.Bucket,
message.PictureSourceKey,
message.PictureDestinationKey,
message.Small,
message.Medium,
message.Large,
correlationId);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
static RoutingSlipBuilder BuildSlip(
Guid albumId,
Guid pictureIntentId,
string bucket,
string pictureSourceKey,
string pictureDestinationKey,
ImageSize small,
ImageSize medium,
ImageSize large,
Guid? correlationId)
{
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
routingSlipBuilder.AddActivity(
ActivityNames.CopyPictureToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
bucket,
pictureSourceKey,
bucket,
pictureDestinationKey));
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
routingSlipBuilder.AddActivity(
ActivityNames.ConsumeUploadIntents,
EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName),
new ConsumeUploadIntentsArguments([pictureIntentId]));
routingSlipBuilder.AddActivity(
ActivityNames.CopyPictureToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
bucket,
pictureSourceKey,
bucket,
pictureDestinationKey));
routingSlipBuilder.AddActivity(
ActivityNames.PublishAlbumPictureProcessingEvent,
EndpointHelper.BuildExecuteActivityUri(PublishAlbumPictureProcessingEventActivity.ExecuteEndpointName),
new PublishAlbumPictureProcessingEventArguments(
albumId,
bucket,
pictureDestinationKey,
small,
medium,
large));
routingSlipBuilder.AddActivity(
ActivityNames.ConsumeUploadIntents,
EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName),
new ConsumeUploadIntentsArguments([pictureIntentId]));
routingSlipBuilder.AddActivity(
ActivityNames.PublishAlbumPictureProcessingEvent,
EndpointHelper.BuildExecuteActivityUri(PublishAlbumPictureProcessingEventActivity.ExecuteEndpointName),
new PublishAlbumPictureProcessingEventArguments(
albumId,
bucket,
pictureDestinationKey,
small,
medium,
large));
return routingSlipBuilder;
}
return routingSlipBuilder;
}
}
@@ -7,66 +7,65 @@ using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
public class AudioWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration)
{
public class AudioWorkflowRoutingSlipBuilder(WorkerConfiguration workerConfiguration)
public RoutingSlipBuilder Build(UpdateTrackAudioEvent message, Guid? correlationId)
{
public RoutingSlipBuilder Build(UpdateTrackAudioEvent message, Guid? correlationId)
{
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
routingSlipBuilder.AddActivity(
ActivityNames.GenerateAudioWorkflowPaths,
EndpointHelper.BuildExecuteActivityUri(GenerateAudioWorkflowPathsActivity.ExecuteEndpointName),
new GenerateAudioWorkflowPathsArguments(
message.TrackId,
workerConfiguration.Routes.TemporaryFilesDirectory,
message.SourceKey));
routingSlipBuilder.AddActivity(
ActivityNames.GenerateAudioWorkflowPaths,
EndpointHelper.BuildExecuteActivityUri(GenerateAudioWorkflowPathsActivity.ExecuteEndpointName),
new GenerateAudioWorkflowPathsArguments(
message.TrackId,
workerConfiguration.Routes.TemporaryFilesDirectory,
message.SourceKey));
routingSlipBuilder.AddActivity(
ActivityNames.DownloadFile,
EndpointHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
new DownloadFileFromBucketArguments(
message.SourceBucket,
message.SourceKey,
RoutingSlipVariableNames.Audio.SourceFilePath));
routingSlipBuilder.AddActivity(
ActivityNames.DownloadFile,
EndpointHelper.BuildExecuteActivityUri(DownloadFileFromBucketActivity.ExecuteEndpointName),
new DownloadFileFromBucketArguments(
message.SourceBucket,
message.SourceKey,
RoutingSlipVariableNames.Audio.SourceFilePath));
routingSlipBuilder.AddActivity(
ActivityNames.TranscodeAudio,
EndpointHelper.BuildExecuteActivityUri(TranscodeAudioActivity.ExecuteEndpointName),
new TranscodeAudioArguments(
message.TrackId,
RoutingSlipVariableNames.Audio.SourceFilePath,
RoutingSlipVariableNames.Workflow.TemporalDirectory));
routingSlipBuilder.AddActivity(
ActivityNames.TranscodeAudio,
EndpointHelper.BuildExecuteActivityUri(TranscodeAudioActivity.ExecuteEndpointName),
new TranscodeAudioArguments(
message.TrackId,
RoutingSlipVariableNames.Audio.SourceFilePath,
RoutingSlipVariableNames.Workflow.TemporalDirectory));
routingSlipBuilder.AddActivity(
ActivityNames.TransferFiles,
EndpointHelper.BuildExecuteActivityUri(TransferFilesToBucketActivity.ExecuteEndpointName),
new TransferFilesToBucketArguments(
message.DestinationBucket,
RoutingSlipVariableNames.Audio.TranscodedDirectory,
message.DestinationFolderKey));
routingSlipBuilder.AddActivity(
ActivityNames.TransferFiles,
EndpointHelper.BuildExecuteActivityUri(TransferFilesToBucketActivity.ExecuteEndpointName),
new TransferFilesToBucketArguments(
message.DestinationBucket,
RoutingSlipVariableNames.Audio.TranscodedDirectory,
message.DestinationFolderKey));
routingSlipBuilder.AddActivity(
ActivityNames.UpdateTrackAudio,
EndpointHelper.BuildExecuteActivityUri(UpdateTrackAudioActivity.ExecuteEndpointName),
new UpdateTrackAudioArguments(
message.TrackId,
message.DestinationFolderKey,
RoutingSlipVariableNames.Audio.DurationSeconds));
routingSlipBuilder.AddActivity(
ActivityNames.UpdateTrackAudio,
EndpointHelper.BuildExecuteActivityUri(UpdateTrackAudioActivity.ExecuteEndpointName),
new UpdateTrackAudioArguments(
message.TrackId,
message.DestinationFolderKey,
RoutingSlipVariableNames.Audio.DurationSeconds));
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.TrackId);
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.TrackAudio);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName),
RoutingSlipEvents.Faulted);
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.SubjectId, message.TrackId);
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.ProcessKind, RoutingSlipVariableNames.ProcessKinds.TrackAudio);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(ProcessingSlipFaultConsumer.QueueName),
RoutingSlipEvents.Faulted);
return routingSlipBuilder;
}
return routingSlipBuilder;
}
}
@@ -7,57 +7,56 @@ using Musify.Infrastructure.MassTransit.Activities.UploadIntents;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
public class CreateTrackRoutingSlipBuilder
{
public class CreateTrackRoutingSlipBuilder
public RoutingSlipBuilder Build(CreateTrackResourcesEvent message, Guid? correlationId)
{
public RoutingSlipBuilder Build(CreateTrackResourcesEvent message, Guid? correlationId)
{
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
routingSlipBuilder.AddActivity(
ActivityNames.CopyPictureToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
message.Bucket,
message.PictureSourceKey,
message.Bucket,
message.PictureDestinationKey));
routingSlipBuilder.AddActivity(
ActivityNames.CopyPictureToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
message.Bucket,
message.PictureSourceKey,
message.Bucket,
message.PictureDestinationKey));
routingSlipBuilder.AddActivity(
ActivityNames.CopyAudioToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
message.Bucket,
message.AudioSourceKey,
message.Bucket,
message.AudioDestinationKey));
routingSlipBuilder.AddActivity(
ActivityNames.CopyAudioToFinal,
EndpointHelper.BuildExecuteActivityUri(CopyFileInBucketActivity.ExecuteEndpointName),
new CopyFileInBucketArguments(
message.Bucket,
message.AudioSourceKey,
message.Bucket,
message.AudioDestinationKey));
routingSlipBuilder.AddActivity(
ActivityNames.ConsumeUploadIntents,
EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName),
new ConsumeUploadIntentsArguments(
[message.PictureIntentId, message.AudioIntentId]));
routingSlipBuilder.AddActivity(
ActivityNames.ConsumeUploadIntents,
EndpointHelper.BuildExecuteActivityUri(ConsumeUploadIntentsActivity.ExecuteEndpointName),
new ConsumeUploadIntentsArguments(
[message.PictureIntentId, message.AudioIntentId]));
routingSlipBuilder.AddActivity(
ActivityNames.PublishTrackProcessingEvents,
EndpointHelper.BuildExecuteActivityUri(PublishTrackProcessingEventsActivity.ExecuteEndpointName),
new PublishTrackProcessingEventsArguments(
message.TrackId,
message.Bucket,
message.PictureDestinationKey,
message.AudioDestinationKey,
message.AudioProcessedFolderKey,
message.Small,
message.Medium,
message.Large));
routingSlipBuilder.AddActivity(
ActivityNames.PublishTrackProcessingEvents,
EndpointHelper.BuildExecuteActivityUri(PublishTrackProcessingEventsActivity.ExecuteEndpointName),
new PublishTrackProcessingEventsArguments(
message.TrackId,
message.Bucket,
message.PictureDestinationKey,
message.AudioDestinationKey,
message.AudioProcessedFolderKey,
message.Small,
message.Medium,
message.Large));
return routingSlipBuilder;
}
return routingSlipBuilder;
}
}
@@ -2,93 +2,92 @@ using MassTransit;
using MassTransit.Courier.Contracts;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Configuration;
using Musify.Application.Contracts;
using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.PlayLists;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Application.Contracts;
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
public class DeletePlayListRoutingSlipBuilder(
IDatabase database,
PlayListConfiguration playListConfiguration,
ApplicationStorageConfiguration storageConfiguration)
{
public class DeletePlayListRoutingSlipBuilder(
IDatabase database,
PlayListConfiguration playListConfiguration,
ApplicationStorageConfiguration storageConfiguration)
public async Task<RoutingSlipBuilder> BuildAsync(
Guid playListId,
long userId,
Guid? correlationId,
CancellationToken cancellationToken)
{
public async Task<RoutingSlipBuilder> BuildAsync(
Guid playListId,
long userId,
Guid? correlationId,
CancellationToken cancellationToken)
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
var playList = await database.PlayLists
.AsNoTracking()
.SingleOrDefaultAsync(p => p.Id == playListId, cancellationToken);
routingSlipBuilder.AddActivity(
ActivityNames.MarkPlayListAsRemoving,
EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsRemovingActivity.ExecuteEndpointName),
new MarkPlayListAsRemovingArguments(playListId));
if (playList?.Pictures != null)
{
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
var bucket = storageConfiguration.Bucket;
var routes = playListConfiguration.Routes;
routingSlipBuilder.AddSubscription(
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
var playList = await database.PlayLists
.AsNoTracking()
.SingleOrDefaultAsync(p => p.Id == playListId, cancellationToken);
routingSlipBuilder.AddActivity(
ActivityNames.MarkPlayListAsRemoving,
EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsRemovingActivity.ExecuteEndpointName),
new MarkPlayListAsRemovingArguments(playListId));
if (playList?.Pictures != null)
if (playList.Pictures.OriginalName is { } originalName)
{
var bucket = storageConfiguration.Bucket;
var routes = playListConfiguration.Routes;
if (playList.Pictures.OriginalName is { } originalName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildOriginalPicturePath(userId, originalName)));
}
if (playList.Pictures.SmallName is { } smallName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListSmallPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildSmallPicturePath(smallName)));
}
if (playList.Pictures.MediumName is { } mediumName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListMediumPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildMediumPicturePath(mediumName)));
}
if (playList.Pictures.LargeName is { } largeName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListLargePicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildLargePicturePath(largeName)));
}
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildOriginalPicturePath(userId, originalName)));
}
routingSlipBuilder.AddActivity(
ActivityNames.DeletePlayListFromDb,
EndpointHelper.BuildExecuteActivityUri(DeletePlayListFromDbActivity.ExecuteEndpointName),
new DeletePlayListFromDbArguments(playListId));
if (playList.Pictures.SmallName is { } smallName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListSmallPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildSmallPicturePath(smallName)));
}
return routingSlipBuilder;
if (playList.Pictures.MediumName is { } mediumName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListMediumPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildMediumPicturePath(mediumName)));
}
if (playList.Pictures.LargeName is { } largeName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListLargePicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildLargePicturePath(largeName)));
}
}
routingSlipBuilder.AddActivity(
ActivityNames.DeletePlayListFromDb,
EndpointHelper.BuildExecuteActivityUri(DeletePlayListFromDbActivity.ExecuteEndpointName),
new DeletePlayListFromDbArguments(playListId));
return routingSlipBuilder;
}
}

Some files were not shown because too many files have changed in this diff Show More