Fix query and API bugs: 403 for non-owners, page bounds, lifecycle filters, cover fallback, race-safe inserts, async album delete
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Infrastructure.MassTransit.Arguments;
|
||||
|
||||
namespace Musify.Infrastructure.MassTransit.Activities.Albums;
|
||||
|
||||
internal class DeleteAlbumFromDbActivity(
|
||||
IDatabase database,
|
||||
ILogger<DeleteAlbumFromDbActivity> logger)
|
||||
: IExecuteActivity<DeleteAlbumFromDbArguments>
|
||||
{
|
||||
public const string ExecuteEndpointName = "delete-album-from-db";
|
||||
|
||||
public async Task<ExecutionResult> Execute(ExecuteContext<DeleteAlbumFromDbArguments> executeContext)
|
||||
{
|
||||
var album = await database.Albums
|
||||
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken);
|
||||
|
||||
if (album == null)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Album {AlbumId} not found while deleting from DB, skipping",
|
||||
executeContext.Arguments.AlbumId);
|
||||
return executeContext.Completed();
|
||||
}
|
||||
|
||||
database.Albums.Remove(album);
|
||||
await database.SaveChangesAsync(executeContext.CancellationToken);
|
||||
|
||||
logger.LogInformation("Deleted album {AlbumId} from DB", album.Id);
|
||||
return executeContext.Completed();
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Domain.ValueObjects;
|
||||
using Musify.Infrastructure.MassTransit.Arguments;
|
||||
|
||||
namespace Musify.Infrastructure.MassTransit.Activities.Albums;
|
||||
|
||||
internal class MarkAlbumAsRemovingActivity(
|
||||
IDatabase database,
|
||||
ILogger<MarkAlbumAsRemovingActivity> logger)
|
||||
: IExecuteActivity<MarkAlbumAsRemovingArguments>
|
||||
{
|
||||
public const string ExecuteEndpointName = "mark-album-as-removing";
|
||||
|
||||
public async Task<ExecutionResult> Execute(ExecuteContext<MarkAlbumAsRemovingArguments> executeContext)
|
||||
{
|
||||
var album = await database.Albums
|
||||
.SingleOrDefaultAsync(p => p.Id == executeContext.Arguments.AlbumId, executeContext.CancellationToken);
|
||||
|
||||
if (album == null)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"Album {AlbumId} not found while marking as removing, skipping",
|
||||
executeContext.Arguments.AlbumId);
|
||||
return executeContext.Completed();
|
||||
}
|
||||
|
||||
album.LifeCycleStatus = LifeCycleStatus.Removing;
|
||||
await database.SaveChangesAsync(executeContext.CancellationToken);
|
||||
|
||||
logger.LogInformation("Marked album {AlbumId} as removing", album.Id);
|
||||
return executeContext.Completed();
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -23,7 +23,8 @@ internal class DownloadFileFromBucketActivity(
|
||||
using var fileStream = await storageHandler.GetFileAsync(
|
||||
executeContext.Arguments.Bucket,
|
||||
executeContext.Arguments.Key,
|
||||
executeContext.CancellationToken);
|
||||
executeContext.CancellationToken)
|
||||
?? throw new FileNotFoundException($"Object {executeContext.Arguments.Bucket}/{executeContext.Arguments.Key} was not found.");
|
||||
|
||||
var destinationDirectory = Path.GetDirectoryName(destinationPath);
|
||||
if (!string.IsNullOrWhiteSpace(destinationDirectory))
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Musify.Infrastructure.MassTransit.Arguments;
|
||||
|
||||
internal record DeleteAlbumFromDbArguments(
|
||||
Guid AlbumId);
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
namespace Musify.Infrastructure.MassTransit.Arguments;
|
||||
|
||||
internal record MarkAlbumAsRemovingArguments(
|
||||
Guid AlbumId);
|
||||
@@ -0,0 +1,24 @@
|
||||
using MassTransit;
|
||||
using Musify.Application.Events;
|
||||
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
|
||||
|
||||
namespace Musify.Infrastructure.MassTransit.Consumers;
|
||||
|
||||
public class DeleteAlbumConsumer(
|
||||
IBus bus,
|
||||
DeleteAlbumRoutingSlipBuilder routingSlipBuilder)
|
||||
: IConsumer<DeleteAlbumEvent>
|
||||
{
|
||||
public async Task Consume(ConsumeContext<DeleteAlbumEvent> consumeContext)
|
||||
{
|
||||
var routingSlip = (await routingSlipBuilder
|
||||
.BuildAsync(
|
||||
consumeContext.Message.AlbumId,
|
||||
consumeContext.Message.UserId,
|
||||
consumeContext.CorrelationId,
|
||||
consumeContext.CancellationToken))
|
||||
.Build();
|
||||
|
||||
await bus.Execute(routingSlip);
|
||||
}
|
||||
}
|
||||
+4
@@ -22,6 +22,7 @@ public static partial class MassTransitDependencyInjection
|
||||
services.AddScoped<PictureWorkflowRoutingSlipBuilder>();
|
||||
services.AddScoped<DeleteTrackRoutingSlipBuilder>();
|
||||
services.AddScoped<DeletePlayListRoutingSlipBuilder>();
|
||||
services.AddScoped<DeleteAlbumRoutingSlipBuilder>();
|
||||
services.AddScoped<CreateTrackRoutingSlipBuilder>();
|
||||
services.AddScoped<PlayListPictureSourceRoutingSlipBuilder>();
|
||||
services.AddScoped<AlbumPictureSourceRoutingSlipBuilder>();
|
||||
@@ -35,6 +36,7 @@ public static partial class MassTransitDependencyInjection
|
||||
options.AddConsumer<UpdateTrackAudioConsumer>();
|
||||
options.AddConsumer<DeleteTrackConsumer>();
|
||||
options.AddConsumer<DeletePlayListConsumer>();
|
||||
options.AddConsumer<DeleteAlbumConsumer>();
|
||||
options.AddConsumer<CreateTrackConsumer>();
|
||||
options.AddConsumer<CreatePlayListConsumer>();
|
||||
options.AddConsumer<UpdatePlayListPictureSourceConsumer>();
|
||||
@@ -58,6 +60,8 @@ public static partial class MassTransitDependencyInjection
|
||||
options.AddExecuteActivity<MarkTrackAsFailedActivity, MarkTrackAsFailedArguments>();
|
||||
options.AddExecuteActivity<MarkPlayListAsFailedActivity, MarkPlayListAsFailedArguments>();
|
||||
options.AddExecuteActivity<MarkAlbumAsFailedActivity, MarkAlbumAsFailedArguments>();
|
||||
options.AddExecuteActivity<MarkAlbumAsRemovingActivity, MarkAlbumAsRemovingArguments>();
|
||||
options.AddExecuteActivity<DeleteAlbumFromDbActivity, DeleteAlbumFromDbArguments>();
|
||||
options.AddExecuteActivity<PublishAlbumPictureProcessingEventActivity, PublishAlbumPictureProcessingEventArguments>();
|
||||
|
||||
options.AddActivity<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>();
|
||||
|
||||
@@ -3,6 +3,6 @@ namespace Musify.Infrastructure.MassTransit.Logs;
|
||||
public record UpdateAlbumPictureLog(
|
||||
Guid AlbumId,
|
||||
string PreviousOriginalPictureKey,
|
||||
string PreviousSmallPictureKey,
|
||||
string PreviousMediumPictureKey,
|
||||
string PreviousLargePictureKey);
|
||||
string? PreviousSmallPictureKey,
|
||||
string? PreviousMediumPictureKey,
|
||||
string? PreviousLargePictureKey);
|
||||
|
||||
@@ -3,6 +3,6 @@ namespace Musify.Infrastructure.MassTransit.Logs;
|
||||
public record UpdatePlayListPictureLog(
|
||||
Guid PlayListId,
|
||||
string PreviousOriginalPictureKey,
|
||||
string PreviousSmallPictureKey,
|
||||
string PreviousMediumPictureKey,
|
||||
string PreviousLargePictureKey);
|
||||
string? PreviousSmallPictureKey,
|
||||
string? PreviousMediumPictureKey,
|
||||
string? PreviousLargePictureKey);
|
||||
|
||||
@@ -9,6 +9,8 @@ internal static class ActivityNames
|
||||
internal const string MarkPlayListAsFailed = "MarkPlayListAsFailed";
|
||||
internal const string DeletePlayListFromDb = "DeletePlayListFromDb";
|
||||
internal const string MarkAlbumAsFailed = "MarkAlbumAsFailed";
|
||||
internal const string MarkAlbumAsRemoving = "MarkAlbumAsRemoving";
|
||||
internal const string DeleteAlbumFromDb = "DeleteAlbumFromDb";
|
||||
|
||||
internal const string GenerateAudioWorkflowPaths = "GenerateAudioWorkflowPaths";
|
||||
internal const string GeneratePictureWorkflowPaths = "GeneratePictureWorkflowPaths";
|
||||
@@ -55,4 +57,7 @@ internal static class ActivityNames
|
||||
internal const string RemovePlayListLargePicture = "RemovePlayListLargePicture";
|
||||
|
||||
internal const string RemoveAlbumOriginalPicture = "RemoveAlbumOriginalPicture";
|
||||
internal const string RemoveAlbumSmallPicture = "RemoveAlbumSmallPicture";
|
||||
internal const string RemoveAlbumMediumPicture = "RemoveAlbumMediumPicture";
|
||||
internal const string RemoveAlbumLargePicture = "RemoveAlbumLargePicture";
|
||||
}
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
using MassTransit;
|
||||
using MassTransit.Courier.Contracts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Infrastructure.MassTransit.Activities.Files;
|
||||
using Musify.Infrastructure.MassTransit.Activities.Albums;
|
||||
using Musify.Infrastructure.MassTransit.Arguments;
|
||||
using Musify.Infrastructure.MassTransit.Consumers;
|
||||
|
||||
namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
|
||||
|
||||
public class DeleteAlbumRoutingSlipBuilder(
|
||||
IDatabase database,
|
||||
AlbumConfiguration albumConfiguration,
|
||||
ApplicationStorageConfiguration storageConfiguration)
|
||||
{
|
||||
public async Task<RoutingSlipBuilder> BuildAsync(
|
||||
Guid albumId,
|
||||
long userId,
|
||||
Guid? correlationId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid());
|
||||
routingSlipBuilder.AddVariable(RoutingSlipVariableNames.Workflow.CorrelationId, correlationId ?? Guid.Empty);
|
||||
|
||||
routingSlipBuilder.AddSubscription(
|
||||
EndpointHelper.BuildConsumerUri(RoutingSlipCleanUpConsumer.QueueName),
|
||||
RoutingSlipEvents.Completed | RoutingSlipEvents.Faulted);
|
||||
|
||||
var album = await database.Albums
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync(p => p.Id == albumId, cancellationToken);
|
||||
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.MarkAlbumAsRemoving,
|
||||
EndpointHelper.BuildExecuteActivityUri(MarkAlbumAsRemovingActivity.ExecuteEndpointName),
|
||||
new MarkAlbumAsRemovingArguments(albumId));
|
||||
|
||||
if (album?.Pictures != null)
|
||||
{
|
||||
var bucket = storageConfiguration.Bucket;
|
||||
var routes = albumConfiguration.Routes;
|
||||
|
||||
if (album.Pictures.OriginalName is { } originalName)
|
||||
{
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.RemoveAlbumOriginalPicture,
|
||||
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
|
||||
new RemoveFileFromBucketArguments(
|
||||
bucket,
|
||||
routes.BuildOriginalPicturePath(userId, originalName)));
|
||||
}
|
||||
|
||||
if (album.Pictures.SmallName is { } smallName)
|
||||
{
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.RemoveAlbumSmallPicture,
|
||||
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
|
||||
new RemoveFileFromBucketArguments(
|
||||
bucket,
|
||||
routes.BuildSmallPicturePath(smallName)));
|
||||
}
|
||||
|
||||
if (album.Pictures.MediumName is { } mediumName)
|
||||
{
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.RemoveAlbumMediumPicture,
|
||||
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
|
||||
new RemoveFileFromBucketArguments(
|
||||
bucket,
|
||||
routes.BuildMediumPicturePath(mediumName)));
|
||||
}
|
||||
|
||||
if (album.Pictures.LargeName is { } largeName)
|
||||
{
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.RemoveAlbumLargePicture,
|
||||
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
|
||||
new RemoveFileFromBucketArguments(
|
||||
bucket,
|
||||
routes.BuildLargePicturePath(largeName)));
|
||||
}
|
||||
}
|
||||
|
||||
routingSlipBuilder.AddActivity(
|
||||
ActivityNames.DeleteAlbumFromDb,
|
||||
EndpointHelper.BuildExecuteActivityUri(DeleteAlbumFromDbActivity.ExecuteEndpointName),
|
||||
new DeleteAlbumFromDbArguments(albumId));
|
||||
|
||||
return routingSlipBuilder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user