Safe code and changes

This commit is contained in:
2026-05-14 01:00:44 +02:00
parent 7f0266d914
commit 8ad203c974
25 changed files with 568 additions and 266 deletions
@@ -62,13 +62,31 @@ namespace Musify.Application.PlayLists.Handlers
{ {
var publishResult = await PublishPictureUpdateEventAsync(playList, cancellationToken); var publishResult = await PublishPictureUpdateEventAsync(playList, cancellationToken);
if (!publishResult.IsSuccess) if (!publishResult.IsSuccess)
{
await TryRollbackOnPublishFailureAsync(playList, cancellationToken);
return publishResult; return publishResult;
}
} }
logger.LogInformation("Created playlist {PlayListId} for user {UserId}", playList.Id, request.UserId); logger.LogInformation("Created playlist {PlayListId} for user {UserId}", playList.Id, request.UserId);
return Result.Created(PlayListResponse.FromEntity(playList)); return Result.Created(PlayListResponse.FromEntity(playList));
} }
async Task TryRollbackOnPublishFailureAsync(PlayList playList, CancellationToken cancellationToken)
{
try
{
database.PlayLists.Remove(playList);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback playlist creation after publish failure for playlist {PlayListId}", playList.Id);
}
await RollbackAllPicturesAsync(playList, cancellationToken);
}
async Task<Result> UploadPictureAsync(PlayList playList, IFileData picture, CancellationToken cancellationToken) async Task<Result> UploadPictureAsync(PlayList playList, IFileData picture, CancellationToken cancellationToken)
{ {
var pictureName = Guid.NewGuid() + picture.FileType; var pictureName = Guid.NewGuid() + picture.FileType;
@@ -123,6 +141,9 @@ namespace Musify.Application.PlayLists.Handlers
if (string.IsNullOrEmpty(playList.OriginalPictureName)) if (string.IsNullOrEmpty(playList.OriginalPictureName))
return; return;
if (playList.OriginalPictureName == playListConfiguration.Routes.PresetOriginalPicture)
return;
var originalPicturePath = playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName); var originalPicturePath = playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName);
try try
@@ -28,25 +28,73 @@ namespace Musify.Application.PlayLists.Handlers
return Result.Unauthorized(); return Result.Unauthorized();
} }
if (!string.IsNullOrWhiteSpace(playList.OriginalPictureName)) var originalPictureName = playList.OriginalPictureName;
await PublishRemoveFileEventAsync(playList.Id, var smallPictureName = playList.SmallPictureName;
playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName), cancellationToken); var mediumPictureName = playList.MediumPictureName;
if (playList.SmallPictureName != playListConfiguration.Routes.PresetSmallPicture) var largePictureName = playList.LargePictureName;
await PublishRemoveFileEventAsync(playList.Id,
playListConfiguration.Routes.BuildSmallPicturePath(playList.SmallPictureName), cancellationToken);
if (playList.MediumPictureName != playListConfiguration.Routes.PresetMediumPicture)
await PublishRemoveFileEventAsync(playList.Id,
playListConfiguration.Routes.BuildMediumPicturePath(playList.MediumPictureName), cancellationToken);
if (playList.LargePictureName != playListConfiguration.Routes.PresetLargePicture)
await PublishRemoveFileEventAsync(playList.Id,
playListConfiguration.Routes.BuildLargePicturePath(playList.LargePictureName), cancellationToken);
database.PlayLists.Remove(playList); var originalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName);
await database.SaveChangesAsync(cancellationToken); var smallPictureKey = playListConfiguration.Routes.BuildSmallPicturePath(playList.SmallPictureName);
var mediumPictureKey = playListConfiguration.Routes.BuildMediumPicturePath(playList.MediumPictureName);
var largePictureKey = playListConfiguration.Routes.BuildLargePicturePath(playList.LargePictureName);
try
{
database.PlayLists.Remove(playList);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to delete playlist {PlayListId}", request.PlayListId);
return Result.Error($"Failed to delete playlist {request.PlayListId}");
}
await PublishRemoveFileEventsBestEffortAsync(
playList.Id,
originalPictureName,
smallPictureName,
mediumPictureName,
largePictureName,
originalPictureKey,
smallPictureKey,
mediumPictureKey,
largePictureKey,
playListConfiguration.Routes,
cancellationToken);
return Result.NoContent(); return Result.NoContent();
} }
async Task PublishRemoveFileEventsBestEffortAsync(
Guid playListId,
string originalPictureName,
string smallPictureName,
string mediumPictureName,
string largePictureName,
string originalPictureKey,
string smallPictureKey,
string mediumPictureKey,
string largePictureKey,
PlayListRoutes playListRoutes,
CancellationToken cancellationToken)
{
if (originalPictureName != playListRoutes.PresetOriginalPicture)
await PublishRemoveFileEventIgnoringErrorsAsync(playListId, originalPictureKey, cancellationToken);
if (smallPictureName != playListRoutes.PresetSmallPicture)
await PublishRemoveFileEventIgnoringErrorsAsync(playListId, smallPictureKey, cancellationToken);
if (mediumPictureName != playListRoutes.PresetMediumPicture)
await PublishRemoveFileEventIgnoringErrorsAsync(playListId, mediumPictureKey, cancellationToken);
if (largePictureName != playListRoutes.PresetLargePicture)
await PublishRemoveFileEventIgnoringErrorsAsync(playListId, largePictureKey, cancellationToken);
}
async Task PublishRemoveFileEventIgnoringErrorsAsync(Guid playListId, string key, CancellationToken cancellationToken)
{
var result = await PublishRemoveFileEventAsync(playListId, key, cancellationToken);
if (!result.IsSuccess)
logger.LogWarning("Remove file event publish failed for playlist {PlayListId} with key {Key}", playListId, key);
}
async Task<Result> PublishRemoveFileEventAsync(Guid playListId, string key, CancellationToken cancellationToken) async Task<Result> PublishRemoveFileEventAsync(Guid playListId, string key, CancellationToken cancellationToken)
{ {
try try
@@ -16,14 +16,16 @@ namespace Musify.Application.PlayLists.Handlers
{ {
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var pageSize = request.PageSize < 1 ? 10 : request.PageSize; var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
var normalizedName = request.Name?.Trim().ToUpperInvariant() ?? string.Empty;
var playListsQuery = database.PlayLists var playListsQuery = database.PlayLists
.AsNoTracking() .AsNoTracking()
.Where(p => p.UserId == request.UserId); .Where(p => p.UserId == request.UserId);
if (!string.IsNullOrWhiteSpace(normalizedName)) if (request.Name is not null)
{
var normalizedName = request.Name.Trim().ToUpperInvariant();
playListsQuery = playListsQuery.Where(p => p.NormalizedName.Contains(normalizedName)); playListsQuery = playListsQuery.Where(p => p.NormalizedName.Contains(normalizedName));
}
var totalCount = await playListsQuery.CountAsync(cancellationToken); var totalCount = await playListsQuery.CountAsync(cancellationToken);
@@ -17,6 +17,9 @@ namespace Musify.Application.PlayLists.Handlers
PlayListConfiguration playListConfiguration) PlayListConfiguration playListConfiguration)
: ICommandHandler<UpdatePlayListCommand, Result<PlayListResponse>> : ICommandHandler<UpdatePlayListCommand, Result<PlayListResponse>>
{ {
record UpdatePictureOutcome(Result Result, bool PictureUpdated, string? UploadedPictureKey);
record PreviousPlayListState(string Name, string NormalizedName, string Description, string OriginalPictureName);
public async ValueTask<Result<PlayListResponse>> Handle(UpdatePlayListCommand request, CancellationToken cancellationToken) public async ValueTask<Result<PlayListResponse>> Handle(UpdatePlayListCommand request, CancellationToken cancellationToken)
{ {
var playListEntity = await database.PlayLists.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken); var playListEntity = await database.PlayLists.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken);
@@ -32,11 +35,14 @@ namespace Musify.Application.PlayLists.Handlers
return Result.Unauthorized(); return Result.Unauthorized();
} }
var previousState = CapturePreviousState(playListEntity);
UpdateName(playListEntity, request.NewName); UpdateName(playListEntity, request.NewName);
UpdateDescription(playListEntity, request.NewDescription); UpdateDescription(playListEntity, request.NewDescription);
var updatePictureResult = await UpdatePicture(playListEntity, request.NewPicture, cancellationToken);
if (!updatePictureResult.IsSuccess) var pictureOutcome = await UpdatePicture(playListEntity, request.NewPicture, cancellationToken);
return updatePictureResult; if (!pictureOutcome.Result.IsSuccess)
return pictureOutcome.Result;
try try
{ {
@@ -45,20 +51,24 @@ namespace Musify.Application.PlayLists.Handlers
} }
catch (Exception exception) catch (Exception exception)
{ {
await Rollback(playListEntity.OriginalPictureName, cancellationToken); if (pictureOutcome.PictureUpdated && pictureOutcome.UploadedPictureKey is not null)
await Rollback(pictureOutcome.UploadedPictureKey, cancellationToken);
logger.LogError(exception, "Failed to update playlist {PlayListId}", playListEntity.Id); logger.LogError(exception, "Failed to update playlist {PlayListId}", playListEntity.Id);
return Result.Error($"Failed to update playlist {playListEntity.Id}"); return Result.Error($"Failed to update playlist {playListEntity.Id}");
} }
try if (pictureOutcome.PictureUpdated)
{ {
await PublishPlayListPictureEvent(playListEntity, cancellationToken); var publishResult = await PublishPlayListPictureEvent(playListEntity, cancellationToken);
await RemoveOldPictures(playListEntity, cancellationToken); if (!publishResult.IsSuccess)
} {
catch (Exception exception) await TryRollbackAfterPublishFailureAsync(
{ playListEntity,
logger.LogError(exception, "Failed to publish playlist picture update event or remove old pictures for playlist {PlayListId}", playListEntity.Id); previousState,
return Result.Error($"Failed to update playlist {playListEntity.Id}"); pictureOutcome.UploadedPictureKey,
cancellationToken);
return Result.Error($"Failed to update playlist {playListEntity.Id}");
}
} }
logger.LogInformation("Updated playlist {PlayListId}", playListEntity.Id); logger.LogInformation("Updated playlist {PlayListId}", playListEntity.Id);
@@ -67,26 +77,34 @@ namespace Musify.Application.PlayLists.Handlers
void UpdateName(PlayList playList, string? newName) void UpdateName(PlayList playList, string? newName)
{ {
if (!string.IsNullOrWhiteSpace(newName)) if (newName is null)
{ return;
playList.Name = newName;
playList.NormalizedName = newName.Trim().ToUpperInvariant(); playList.Name = newName;
} playList.NormalizedName = newName.Trim().ToUpperInvariant();
} }
void UpdateDescription(PlayList playList, string? newDescription) void UpdateDescription(PlayList playList, string? newDescription)
{ {
if (!string.IsNullOrWhiteSpace(newDescription)) if (newDescription is null)
playList.Description = newDescription; return;
playList.Description = newDescription;
} }
async Task<Result> UpdatePicture(PlayList playList, IFileData? newPicture, CancellationToken cancellationToken) static PreviousPlayListState CapturePreviousState(PlayList playList)
=> new(playList.Name, playList.NormalizedName, playList.Description, playList.OriginalPictureName);
async Task<UpdatePictureOutcome> UpdatePicture(
PlayList playList,
IFileData? newPicture,
CancellationToken cancellationToken)
{ {
if (newPicture is null) if (newPicture is null)
return Result.Success(); return new UpdatePictureOutcome(Result.Success(), false, null);
var pictureName = Guid.NewGuid() + newPicture.FileType; var pictureName = Guid.NewGuid() + newPicture.FileType;
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName); var pictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(pictureName);
try try
{ {
await storageHandler.UploadFileAsync( await storageHandler.UploadFileAsync(
@@ -98,11 +116,11 @@ namespace Musify.Application.PlayLists.Handlers
} }
catch catch
{ {
return Result.Error("Failed to upload picture to storage"); return new UpdatePictureOutcome(Result.Error("Failed to upload picture to storage"), false, null);
} }
playList.OriginalPictureName = pictureName; playList.OriginalPictureName = pictureName;
return Result.Success(); return new UpdatePictureOutcome(Result.Success(), true, pictureKey);
} }
async Task<Result> PublishPlayListPictureEvent(PlayList playList, CancellationToken cancellationToken) async Task<Result> PublishPlayListPictureEvent(PlayList playList, CancellationToken cancellationToken)
@@ -112,7 +130,7 @@ namespace Musify.Application.PlayLists.Handlers
await eventBus.PublishAsync(new UpdatePlayListPictureEvent( await eventBus.PublishAsync(new UpdatePlayListPictureEvent(
playList.Id, playList.Id,
storageConfiguration.Bucket, storageConfiguration.Bucket,
Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, playList.OriginalPictureName), playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName),
new ImageSize( new ImageSize(
playListConfiguration.Routes.SmallPicturesPath, playListConfiguration.Routes.SmallPicturesPath,
playListConfiguration.PicturesSizes.SmallPictureWidth, playListConfiguration.PicturesSizes.SmallPictureWidth,
@@ -135,25 +153,6 @@ namespace Musify.Application.PlayLists.Handlers
} }
} }
async Task RemoveOldPictures(PlayList playList, CancellationToken cancellationToken)
{
if (playList.SmallPictureName != playListConfiguration.Routes.PresetSmallPicture)
await PublishRemoveFileEvent(Path.Combine(
playListConfiguration.Routes.SmallPicturesPath,
playList.SmallPictureName),
cancellationToken);
if (playList.MediumPictureName != playListConfiguration.Routes.PresetMediumPicture)
await PublishRemoveFileEvent(Path.Combine(
playListConfiguration.Routes.MediumPicturesPath,
playList.MediumPictureName),
cancellationToken);
if (playList.LargePictureName != playListConfiguration.Routes.PresetLargePicture)
await PublishRemoveFileEvent(Path.Combine(
playListConfiguration.Routes.LargePicturesPath,
playList.LargePictureName),
cancellationToken);
}
async Task Rollback(string pictureKey, CancellationToken cancellationToken) async Task Rollback(string pictureKey, CancellationToken cancellationToken)
{ {
await storageHandler.RemoveFileAsync( await storageHandler.RemoveFileAsync(
@@ -163,19 +162,36 @@ namespace Musify.Application.PlayLists.Handlers
logger.LogInformation("Rolled back uploaded picture {PictureKey} from storage", pictureKey); logger.LogInformation("Rolled back uploaded picture {PictureKey} from storage", pictureKey);
} }
async Task<Result> PublishRemoveFileEvent(string key, CancellationToken cancellationToken) async Task TryRollbackAfterPublishFailureAsync(
PlayList playList,
PreviousPlayListState previousState,
string? uploadedPictureKey,
CancellationToken cancellationToken)
{ {
playList.Name = previousState.Name;
playList.NormalizedName = previousState.NormalizedName;
playList.Description = previousState.Description;
playList.OriginalPictureName = previousState.OriginalPictureName;
try try
{ {
await eventBus.PublishAsync(new RemoveFileEvent( await database.SaveChangesAsync(cancellationToken);
storageConfiguration.Bucket,
key), cancellationToken);
return Result.NoContent();
} }
catch (Exception exception) catch (Exception exception)
{ {
logger.LogError(exception, "Failed to publish remove file event for key {Key}", key); logger.LogError(exception, "Failed to rollback playlist state after publish failure for playlist {PlayListId}", playList.Id);
return Result.Error($"Failed to publish remove file event for {key}"); }
if (uploadedPictureKey is null)
return;
try
{
await Rollback(uploadedPictureKey, cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback uploaded picture after publish failure for playlist {PlayListId}", playList.Id);
} }
} }
} }
@@ -7,7 +7,7 @@ namespace Musify.Application.PlayLists.Queries
{ {
public record GetPlayListsByUserIdQuery( public record GetPlayListsByUserIdQuery(
Guid UserId, Guid UserId,
string Name = "", string? Name = null,
int PageNumber = 1, int PageNumber = 1,
int PageSize = 10) int PageSize = 10)
: IQuery<Result<PaginatedResponse<PlayListResponse>> >; : IQuery<Result<PaginatedResponse<PlayListResponse>> >;
@@ -34,13 +34,35 @@ namespace Musify.Application.Tracks.Handler
var fullPictureKey = trackConfiguration.Routes.BuildOriginalPicturePath(trackEntity.OriginalPictureName); var fullPictureKey = trackConfiguration.Routes.BuildOriginalPicturePath(trackEntity.OriginalPictureName);
var fullAudioKey = trackConfiguration.Routes.BuildOriginalAudioPath(trackEntity.OriginalAudioName); var fullAudioKey = trackConfiguration.Routes.BuildOriginalAudioPath(trackEntity.OriginalAudioName);
await UploadFilesAsync(request, fullPictureKey, fullAudioKey, cancellationToken); var uploadResult = await UploadFilesAsync(request, fullPictureKey, fullAudioKey, cancellationToken);
if (!uploadResult.IsSuccess)
return uploadResult;
await PublishUpdateEvent(trackEntity, cancellationToken); try
await PublishTranscodeEvent(trackEntity, cancellationToken); {
await database.Tracks.AddAsync(trackEntity, cancellationToken);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to save track {Title} after uploading files", request.Title);
await RollbackFilesAsync(fullPictureKey, fullAudioKey, cancellationToken);
return Result.Error($"Failed to create track for {request.Title}");
}
await database.Tracks.AddAsync(trackEntity, cancellationToken); var publishPictureResult = await PublishUpdateEvent(trackEntity, cancellationToken);
await database.SaveChangesAsync(cancellationToken); if (!publishPictureResult.IsSuccess)
{
await RollbackTrackAfterPublishFailureAsync(trackEntity, fullPictureKey, fullAudioKey, cancellationToken);
return Result.Error($"Failed to create track for {request.Title}");
}
var publishTranscodeResult = await PublishTranscodeEvent(trackEntity, cancellationToken);
if (!publishTranscodeResult.IsSuccess)
{
await RollbackTrackAfterPublishFailureAsync(trackEntity, fullPictureKey, fullAudioKey, cancellationToken);
return Result.Error($"Failed to create track for {request.Title}");
}
return Result.Created(TrackResponse.FromEntity(trackEntity)); return Result.Created(TrackResponse.FromEntity(trackEntity));
} }
@@ -91,7 +113,7 @@ namespace Musify.Application.Tracks.Handler
logger.LogInformation("Rolled back uploaded files {PictureKey}, {AudioKey}", pictureKey, audioKey); logger.LogInformation("Rolled back uploaded files {PictureKey}, {AudioKey}", pictureKey, audioKey);
} }
async Task PublishUpdateEvent(Track track, CancellationToken cancellationToken) async Task<Result> PublishUpdateEvent(Track track, CancellationToken cancellationToken)
{ {
try try
{ {
@@ -111,14 +133,16 @@ namespace Musify.Application.Tracks.Handler
trackConfiguration.Routes.LargePicturesPath, trackConfiguration.Routes.LargePicturesPath,
trackConfiguration.PicturesSizes.LargePictureWidth, trackConfiguration.PicturesSizes.LargePictureWidth,
trackConfiguration.PicturesSizes.LargePictureHeight)), cancellationToken); trackConfiguration.PicturesSizes.LargePictureHeight)), cancellationToken);
return Result.Success();
} }
catch (Exception exception) catch (Exception exception)
{ {
logger.LogError(exception, "Failed to publish track picture update event for track {TrackId}", track.Id); logger.LogError(exception, "Failed to publish track picture update event for track {TrackId}", track.Id);
return Result.Error($"Failed to publish track picture update event for track {track.Id}");
} }
} }
async Task PublishTranscodeEvent(Track track, CancellationToken cancellationToken) async Task<Result> PublishTranscodeEvent(Track track, CancellationToken cancellationToken)
{ {
var destinationFolderAudio = trackConfiguration.Routes.BuildProcessedAudioPath(Guid.NewGuid().ToString()); var destinationFolderAudio = trackConfiguration.Routes.BuildProcessedAudioPath(Guid.NewGuid().ToString());
@@ -130,10 +154,38 @@ namespace Musify.Application.Tracks.Handler
trackConfiguration.Routes.BuildOriginalAudioPath(track.OriginalAudioName), trackConfiguration.Routes.BuildOriginalAudioPath(track.OriginalAudioName),
storageConfiguration.Bucket, storageConfiguration.Bucket,
destinationFolderAudio), cancellationToken); destinationFolderAudio), cancellationToken);
return Result.Success();
} }
catch (Exception exception) catch (Exception exception)
{ {
logger.LogError(exception, "Failed to publish track audio update event for track {TrackId}", track.Id); logger.LogError(exception, "Failed to publish track audio update event for track {TrackId}", track.Id);
return Result.Error($"Failed to publish track audio update event for track {track.Id}");
}
}
async Task RollbackTrackAfterPublishFailureAsync(
Track track,
string pictureKey,
string audioKey,
CancellationToken cancellationToken)
{
try
{
database.Tracks.Remove(track);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback track {TrackId} persistence after publish failure", track.Id);
}
try
{
await RollbackFilesAsync(pictureKey, audioKey, cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to rollback uploaded files after publish failure for track {TrackId}", track.Id);
} }
} }
} }
@@ -24,7 +24,10 @@ namespace Musify.Application.Tracks.Handler
return Result.NotFound(); return Result.NotFound();
} }
if (track.Id != request.UserId) var isOwner = await database.UserHasTracks
.AsNoTracking()
.AnyAsync(ut => ut.TrackId == request.TrackId && ut.UserId == request.UserId, cancellationToken);
if (!isOwner)
{ {
logger.LogWarning("User {UserId} unauthorized to delete track {TrackId}", request.UserId, request.TrackId); logger.LogWarning("User {UserId} unauthorized to delete track {TrackId}", request.UserId, request.TrackId);
return Result.Unauthorized(); return Result.Unauthorized();
@@ -37,13 +40,46 @@ namespace Musify.Application.Tracks.Handler
return Result.Conflict("Track is currently being processed and cannot be deleted"); return Result.Conflict("Track is currently being processed and cannot be deleted");
} }
var previousPicturesStatus = track.PicturesProcessingStatus;
var previousAudioStatus = track.AudioTranscodeProcessingStatus;
track.PicturesProcessingStatus = ProcessingStatus.Processing; track.PicturesProcessingStatus = ProcessingStatus.Processing;
track.AudioTranscodeProcessingStatus = ProcessingStatus.Processing; track.AudioTranscodeProcessingStatus = ProcessingStatus.Processing;
await database.SaveChangesAsync(cancellationToken);
await eventBus.PublishAsync( try
new DeleteTrackEvent(track.Id, request.UserId), {
cancellationToken); await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to mark track {TrackId} as processing before deletion", request.TrackId);
return Result.Error($"Failed to delete track {request.TrackId}");
}
try
{
await eventBus.PublishAsync(
new DeleteTrackEvent(track.Id, request.UserId),
cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to publish delete track event for track {TrackId}", request.TrackId);
track.PicturesProcessingStatus = previousPicturesStatus;
track.AudioTranscodeProcessingStatus = previousAudioStatus;
try
{
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception rollbackException)
{
logger.LogError(rollbackException, "Failed to rollback track {TrackId} processing flags after publish failure", request.TrackId);
}
return Result.Error($"Failed to delete track {request.TrackId}");
}
return Result.NoContent(); return Result.NoContent();
} }
@@ -16,15 +16,17 @@ namespace Musify.Application.Tracks.Handler
{ {
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var pageSize = request.PageSize < 1 ? 10 : request.PageSize; var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
var normalizedName = request.Name?.Trim().ToUpperInvariant() ?? string.Empty;
var tracksQuery = database.UserHasTracks var tracksQuery = database.UserHasTracks
.AsNoTracking() .AsNoTracking()
.Include(ut => ut.Track) .Include(ut => ut.Track)
.Where(p => p.UserId == request.UserId); .Where(p => p.UserId == request.UserId);
if (!string.IsNullOrWhiteSpace(normalizedName)) if (request.Name is not null)
{
var normalizedName = request.Name.Trim().ToUpperInvariant();
tracksQuery = tracksQuery.Where(t => t.Track.NormalizedTitle.Contains(normalizedName)); tracksQuery = tracksQuery.Where(t => t.Track.NormalizedTitle.Contains(normalizedName));
}
var totalCount = await tracksQuery.CountAsync(cancellationToken); var totalCount = await tracksQuery.CountAsync(cancellationToken);
@@ -17,15 +17,16 @@ namespace Musify.Application.Tracks.Handler
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var pageSize = request.PageSize < 1 ? 10 : request.PageSize; var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
var totalCount = await database.PlayLists.CountAsync(cancellationToken); var tracksQuery = database.UserHasTracks
var pagedPlayLists = await database.UserHasTracks
.AsNoTracking() .AsNoTracking()
.OrderBy(t => t.Id) .OrderBy(t => t.Id)
.Include(ut => ut.Track) .Include(ut => ut.Track)
.Select(t => TrackResponse.FromEntity(t.Track)) .Select(t => TrackResponse.FromEntity(t.Track));
.ToPagedListAsync(pageNumber, pageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<TrackResponse>.FromPagedList(pagedPlayLists)); var totalCount = await tracksQuery.CountAsync(cancellationToken);
var pagedTracks = await tracksQuery.ToPagedListAsync(pageNumber, pageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<TrackResponse>.FromPagedList(pagedTracks));
} }
} }
} }
@@ -7,7 +7,7 @@ namespace Musify.Application.Tracks.Queries
{ {
public record GetTracksByUserIdQuery( public record GetTracksByUserIdQuery(
Guid UserId, Guid UserId,
string Name = "", string? Name = null,
int PageNumber = 1, int PageNumber = 1,
int PageSize = 10) int PageSize = 10)
: IQuery<Result<PaginatedResponse<TrackResponse> > >; : IQuery<Result<PaginatedResponse<TrackResponse> > >;
@@ -21,8 +21,17 @@ namespace Musify.Application.Users.Handlers
} }
var newUser = CreateUserCommand.ToEntity(request); var newUser = CreateUserCommand.ToEntity(request);
await database.Users.AddAsync(newUser, cancellationToken);
await database.SaveChangesAsync(cancellationToken); try
{
await database.Users.AddAsync(newUser, cancellationToken);
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to create user {UserId}", request.Id);
return Result.Error($"Failed to create user {request.Id}");
}
logger.LogInformation("Created user {UserId}", newUser.Id); logger.LogInformation("Created user {UserId}", newUser.Id);
return Result.Created(UserResponse.FromEntity(newUser)); return Result.Created(UserResponse.FromEntity(newUser));
@@ -6,7 +6,6 @@ using Musify.Application.Contracts.Infrastructure;
using Musify.Application.Users.Queries; using Musify.Application.Users.Queries;
using Musify.Application.Users.Responses; using Musify.Application.Users.Responses;
using X.PagedList.EF; using X.PagedList.EF;
using X.PagedList.Extensions;
namespace Musify.Application.Users.Handlers namespace Musify.Application.Users.Handlers
{ {
@@ -17,12 +16,12 @@ namespace Musify.Application.Users.Handlers
{ {
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
var pageSize = request.PageSize < 1 ? 10 : request.PageSize; var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
var first = (pageNumber - 1) * pageSize;
var user = database.Users var user = database.Users
.AsNoTracking() .AsNoTracking()
.AsQueryable(); .AsQueryable();
if (!string.IsNullOrEmpty(request.UsernameSearch))
if (request.UsernameSearch is not null)
{ {
var normalizedUsername = request.UsernameSearch.Trim().ToUpperInvariant(); var normalizedUsername = request.UsernameSearch.Trim().ToUpperInvariant();
user = user.Where(u => u.NormalizedName.Contains(normalizedUsername)); user = user.Where(u => u.NormalizedName.Contains(normalizedUsername));
+1 -1
View File
@@ -5,6 +5,6 @@ using Musify.Application.Users.Responses;
namespace Musify.Application.Users.Queries namespace Musify.Application.Users.Queries
{ {
public record GetUsersQuery(int PageNumber, int PageSize, string UsernameSearch) public record GetUsersQuery(int PageNumber, int PageSize, string? UsernameSearch = null)
: IQuery<Result<PaginatedResponse<UserResponse> >>; : IQuery<Result<PaginatedResponse<UserResponse> >>;
} }
@@ -1,6 +1,5 @@
using MassTransit; using MassTransit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MimeMapping;
using Musify.Application.Contracts.Infrastructure; using Musify.Application.Contracts.Infrastructure;
using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs; using Musify.Infrastructure.MassTransit.Logs;
@@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Contracts.Infrastructure; using Musify.Application.Contracts.Infrastructure;
using Musify.Infrastructure.MassTransit.Activities.Arguments; using Musify.Infrastructure.MassTransit.Activities.Arguments;
using Musify.Infrastructure.MassTransit.Activities.Logs; using Musify.Infrastructure.MassTransit.Activities.Logs;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit.Activities namespace Musify.Infrastructure.MassTransit.Activities
{ {
@@ -1,7 +1,6 @@
using MassTransit; using MassTransit;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Musify.Application.Contracts.Infrastructure; using Musify.Application.Contracts.Infrastructure;
using Musify.Domain.Entities;
using Musify.Infrastructure.MassTransit.Activities.Arguments; using Musify.Infrastructure.MassTransit.Activities.Arguments;
using Musify.Infrastructure.MassTransit.Activities.Logs; using Musify.Infrastructure.MassTransit.Activities.Logs;
@@ -0,0 +1,40 @@
using MassTransit;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts.Infrastructure;
using Musify.Application.Events;
namespace Musify.Infrastructure.MassTransit.Consumers
{
public class RemoveFileConsumer(
IStorageService storageService,
ILogger<RemoveFileConsumer> logger)
: IConsumer<RemoveFileEvent>
{
public const string QueueName = "remove-file";
public async Task Consume(ConsumeContext<RemoveFileEvent> consumeContext)
{
try
{
await storageService.RemoveFileAsync(
consumeContext.Message.Bucket,
consumeContext.Message.Key,
consumeContext.CancellationToken);
logger.LogInformation(
"Removed file {Key} from bucket {Bucket}",
consumeContext.Message.Key,
consumeContext.Message.Bucket);
}
catch (Exception exception)
{
logger.LogError(
exception,
"Failed to remove file {Key} from bucket {Bucket}",
consumeContext.Message.Key,
consumeContext.Message.Bucket);
throw;
}
}
}
}
@@ -0,0 +1,50 @@
using MassTransit;
using Musify.Infrastructure.MassTransit.Consumers;
namespace Musify.Infrastructure.MassTransit
{
public static partial class MassTransitDependencyInjection
{
static void ConfigureConsumerEndpoint<TConsumer>(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
IBusRegistrationContext busRegistrationContext,
string queueName)
where TConsumer : class, IConsumer
{
busFactoryConfigurator.ReceiveEndpoint(queueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<TConsumer>(busRegistrationContext));
}
static void ConfigureExecuteActivityEndpoint<TActivity, TArguments>(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
IBusRegistrationContext busRegistrationContext,
string endpointName)
where TActivity : class, IExecuteActivity<TArguments>
where TArguments : class
{
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.ExecuteQueue(endpointName), endpointConfigurator =>
{
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(busRegistrationContext);
});
}
static void ConfigureActivityEndpoint<TActivity, TArguments, TLog>(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
IBusRegistrationContext busRegistrationContext,
string endpointName)
where TActivity : class, IActivity<TArguments, TLog>
where TArguments : class
where TLog : class
{
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.ExecuteQueue(endpointName), endpointConfigurator =>
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(
EndpointHelper.BuildCompensateActivityUri(endpointName),
busRegistrationContext)
);
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.CompensateQueue(endpointName), endpointConfigurator =>
endpointConfigurator.CompensateActivityHost<TActivity, TLog>(busRegistrationContext)
);
}
}
}
@@ -0,0 +1,24 @@
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();
services.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<TOptions>>().Value);
}
}
}
@@ -0,0 +1,144 @@
using MassTransit;
using Microsoft.Extensions.DependencyInjection;
using Musify.Infrastructure.Configuration;
using Musify.Infrastructure.MassTransit.Activities;
using Musify.Infrastructure.MassTransit.Activities.Arguments;
using Musify.Infrastructure.MassTransit.Activities.Audio;
using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.Logs;
using Musify.Infrastructure.MassTransit.Activities.Tracks;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Infrastructure.MassTransit.Logs;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
namespace Musify.Infrastructure.MassTransit
{
public static partial class MassTransitDependencyInjection
{
static void RegisterRoutingSlipBuilders(IServiceCollection services)
{
services.AddScoped<AudioWorkflowRoutingSlipBuilder>();
services.AddScoped<PictureWorkflowRoutingSlipBuilder>();
services.AddScoped<DeleteTrackRoutingSlipBuilder>();
}
static void RegisterConsumersAndActivities(IBusRegistrationConfigurator options)
{
options.AddConsumer<UpdatePlayListPictureConsumer>();
options.AddConsumer<UpdateTrackPictureConsumer>();
options.AddConsumer<RoutingSlipCleanUpConsumer>();
options.AddConsumer<UpdateTrackAudioConsumer>();
options.AddConsumer<DeleteTrackConsumer>();
options.AddConsumer<RemoveFileConsumer>();
options.AddExecuteActivity<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>();
options.AddExecuteActivity<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>();
options.AddExecuteActivity<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>();
options.AddExecuteActivity<DeleteTrackActivity, DeleteTrackArguments>();
options.AddActivity<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>();
options.AddActivity<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>();
options.AddActivity<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>();
options.AddActivity<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>();
options.AddActivity<TranscodeDashAudioActivity, TranscodeDashAudioArguments, TranscodeDashAudioLog>();
options.AddActivity<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>();
options.AddActivity<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>();
options.AddActivity<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>();
}
static void ConfigureRabbitMqWorkerEndpoints(
IBusRegistrationContext busRegistrationContext,
IRabbitMqBusFactoryConfigurator busFactoryConfigurator)
{
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>());
ConfigureConsumerEndpoint<UpdatePlayListPictureConsumer>(
busFactoryConfigurator,
busRegistrationContext,
UpdatePlayListPictureConsumer.QueueName);
ConfigureConsumerEndpoint<UpdateTrackAudioConsumer>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackAudioConsumer.QueueName);
ConfigureConsumerEndpoint<UpdateTrackPictureConsumer>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackPictureConsumer.QueueName);
ConfigureConsumerEndpoint<DeleteTrackConsumer>(
busFactoryConfigurator,
busRegistrationContext,
DeleteTrackConsumer.QueueName);
ConfigureConsumerEndpoint<RemoveFileConsumer>(
busFactoryConfigurator,
busRegistrationContext,
RemoveFileConsumer.QueueName);
ConfigureConsumerEndpoint<RoutingSlipCleanUpConsumer>(
busFactoryConfigurator,
busRegistrationContext,
RoutingSlipCleanUpConsumer.QueueName);
ConfigureExecuteActivityEndpoint<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>(
busFactoryConfigurator,
busRegistrationContext,
RemoveFileFromBucketActivity.ExecuteEndpointName);
ConfigureExecuteActivityEndpoint<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>(
busFactoryConfigurator,
busRegistrationContext,
GenerateAudioWorkflowPathsActivity.ExecuteEndpointName);
ConfigureExecuteActivityEndpoint<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>(
busFactoryConfigurator,
busRegistrationContext,
GeneratePictureWorkflowPathsActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>(
busFactoryConfigurator,
busRegistrationContext,
ResizePictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdatePlayListPictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackPictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
DownloadFileFromBucketActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<TranscodeDashAudioActivity, TranscodeDashAudioArguments, TranscodeDashAudioLog>(
busFactoryConfigurator,
busRegistrationContext,
TranscodeDashAudioActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
TransferFilesToBucketActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackAudioActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
UploadFileToBucketActivity.ExecuteEndpointName);
}
}
}
@@ -1,33 +1,19 @@
using MassTransit; using MassTransit;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Musify.Application.Contracts.Infrastructure; using Musify.Application.Contracts.Infrastructure;
using Musify.Infrastructure.Configuration; using Musify.Infrastructure.Configuration;
using Musify.Infrastructure.MassTransit.Activities;
using Musify.Infrastructure.MassTransit.Activities.Arguments;
using Musify.Infrastructure.MassTransit.Activities.Audio;
using Musify.Infrastructure.MassTransit.Activities.Files;
using Musify.Infrastructure.MassTransit.Activities.Logs;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Infrastructure.MassTransit.Logs;
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
using Musify.Infrastructure.MassTransit.Activities.Tracks;
namespace Musify.Infrastructure.MassTransit namespace Musify.Infrastructure.MassTransit
{ {
public static 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)
{ {
serviceDescriptors RegisterValidatedOptions<MassTransitConfiguration>(
.AddOptionsWithValidateOnStart<MassTransitConfiguration>() serviceDescriptors,
.Bind(configuration.GetRequiredSection(MassTransitConfiguration.SectionName)) configuration,
.ValidateDataAnnotations(); MassTransitConfiguration.SectionName);
serviceDescriptors.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<MassTransitConfiguration>>().Value);
serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>(); serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>();
serviceDescriptors.AddMassTransit(options => serviceDescriptors.AddMassTransit(options =>
@@ -43,124 +29,24 @@ namespace Musify.Infrastructure.MassTransit
public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors, IConfiguration configuration) public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors, IConfiguration configuration)
{ {
serviceDescriptors RegisterValidatedOptions<MassTransitConfiguration>(
.AddOptionsWithValidateOnStart<MassTransitConfiguration>() serviceDescriptors,
.Bind(configuration.GetRequiredSection(MassTransitConfiguration.SectionName)) configuration,
.ValidateDataAnnotations(); MassTransitConfiguration.SectionName);
serviceDescriptors.AddSingleton(serviceProvider => RegisterValidatedOptions<WorkerConfiguration>(
serviceProvider.GetRequiredService<IOptions<MassTransitConfiguration>>().Value); serviceDescriptors,
configuration,
WorkerConfiguration.SectionName);
serviceDescriptors RegisterRoutingSlipBuilders(serviceDescriptors);
.AddOptionsWithValidateOnStart<WorkerConfiguration>()
.Bind(configuration.GetRequiredSection(WorkerConfiguration.SectionName))
.ValidateDataAnnotations();
serviceDescriptors.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IOptions<WorkerConfiguration>>().Value);
serviceDescriptors.AddScoped<AudioWorkflowRoutingSlipBuilder>();
serviceDescriptors.AddScoped<PictureWorkflowRoutingSlipBuilder>();
serviceDescriptors.AddScoped<DeleteTrackRoutingSlipBuilder>();
serviceDescriptors.AddMassTransit(options => serviceDescriptors.AddMassTransit(options =>
{ {
options.AddConsumer<UpdatePlayListPictureConsumer>(); RegisterConsumersAndActivities(options);
options.AddConsumer<UpdateTrackPictureConsumer>();
options.AddConsumer<RoutingSlipCleanUpConsumer>();
options.AddConsumer<UpdateTrackAudioConsumer>();
options.AddConsumer<DeleteTrackConsumer>();
options.AddExecuteActivity<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>();
options.AddExecuteActivity<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>();
options.AddExecuteActivity<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>();
options.AddExecuteActivity<DeleteTrackActivity, DeleteTrackArguments>();
options.AddActivity<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>();
options.AddActivity<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>();
options.AddActivity<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>();
options.AddActivity<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>();
options.AddActivity<TranscodeDashAudioActivity, TranscodeDashAudioArguments, TranscodeDashAudioLog>();
options.AddActivity<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>();
options.AddActivity<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>();
options.AddActivity<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>();
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) => options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
{ ConfigureRabbitMqWorkerEndpoints(busRegistrationContext, busFactoryConfigurator));
ConfigureRabbitMqHost(
busFactoryConfigurator,
busRegistrationContext.GetRequiredService<MassTransitConfiguration>());
busFactoryConfigurator.ReceiveEndpoint(UpdatePlayListPictureConsumer.QueueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<UpdatePlayListPictureConsumer>(busRegistrationContext));
busFactoryConfigurator.ReceiveEndpoint(UpdateTrackAudioConsumer.QueueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<UpdateTrackAudioConsumer>(busRegistrationContext));
busFactoryConfigurator.ReceiveEndpoint(UpdateTrackPictureConsumer.QueueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<UpdateTrackPictureConsumer>(busRegistrationContext));
busFactoryConfigurator.ReceiveEndpoint(DeleteTrackConsumer.QueueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<DeleteTrackConsumer>(busRegistrationContext));
busFactoryConfigurator.ReceiveEndpoint(RoutingSlipCleanUpConsumer.QueueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<RoutingSlipCleanUpConsumer>(busRegistrationContext));
ConfigureExecuteActivityEndpoint<RemoveFileFromBucketActivity, RemoveFileFromBucketArguments>(
busFactoryConfigurator,
busRegistrationContext,
RemoveFileFromBucketActivity.ExecuteEndpointName);
ConfigureExecuteActivityEndpoint<GenerateAudioWorkflowPathsActivity, GenerateAudioWorkflowPathsArguments>(
busFactoryConfigurator,
busRegistrationContext,
GenerateAudioWorkflowPathsActivity.ExecuteEndpointName);
ConfigureExecuteActivityEndpoint<GeneratePictureWorkflowPathsActivity, GeneratePictureWorkflowPathsArguments>(
busFactoryConfigurator,
busRegistrationContext,
GeneratePictureWorkflowPathsActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<ResizePictureActivity, ResizePictureLocalArguments, ResizePictureLog>(
busFactoryConfigurator,
busRegistrationContext,
ResizePictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdatePlayListPictureActivity, UpdatePlayListPictureArguments, UpdatePlayListPictureLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdatePlayListPictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdateTrackPictureActivity, UpdateTrackPictureArguments, UpdateTrackPictureLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackPictureActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<DownloadFileFromBucketActivity, DownloadFileFromBucketArguments, DownloadFileFromBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
DownloadFileFromBucketActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<TranscodeDashAudioActivity, TranscodeDashAudioArguments, TranscodeDashAudioLog>(
busFactoryConfigurator,
busRegistrationContext,
TranscodeDashAudioActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<TransferFilesToBucketActivity, TransferFilesToBucketArguments, TransferFilesToBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
TransferFilesToBucketActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UpdateTrackAudioActivity, UpdateTrackAudioArguments, UpdateTrackAudioLog>(
busFactoryConfigurator,
busRegistrationContext,
UpdateTrackAudioActivity.ExecuteEndpointName);
ConfigureActivityEndpoint<UploadFileToBucketActivity, UploadFileToBucketArguments, UploadFileToBucketLog>(
busFactoryConfigurator,
busRegistrationContext,
UploadFileToBucketActivity.ExecuteEndpointName);
} );
} ); } );
return serviceDescriptors; return serviceDescriptors;
@@ -176,37 +62,5 @@ namespace Musify.Infrastructure.MassTransit
options.Password(massTransitConfiguration.Password); options.Password(massTransitConfiguration.Password);
} ); } );
} }
static void ConfigureExecuteActivityEndpoint<TActivity, TArguments>(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
IBusRegistrationContext busRegistrationContext,
string endpointName)
where TActivity : class, IExecuteActivity<TArguments>
where TArguments : class
{
busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_execute", endpointConfigurator =>
{
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(busRegistrationContext);
} );
}
static void ConfigureActivityEndpoint<TActivity, TArguments, TLog>(
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
IBusRegistrationContext busRegistrationContext,
string endpointName)
where TActivity : class, IActivity<TArguments, TLog>
where TArguments : class
where TLog : class
{
busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_execute", endpointConfigurator =>
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(
EndpointHelper.BuildCompensateActivityUri(endpointName),
busRegistrationContext)
);
busFactoryConfigurator.ReceiveEndpoint($"{endpointName}_compensate", endpointConfigurator =>
endpointConfigurator.CompensateActivityHost<TActivity, TLog>(busRegistrationContext)
);
}
} }
} }
@@ -0,0 +1,9 @@
namespace Musify.Infrastructure.MassTransit
{
internal static class MassTransitEndpointNames
{
internal static string ExecuteQueue(string endpointName) => $"{endpointName}_execute";
internal static string CompensateQueue(string endpointName) => $"{endpointName}_compensate";
}
}
@@ -3,7 +3,7 @@ namespace Musify.Infrastructure.MassTransit.Activities.Logs
public record UpdateTrackPictureLog( public record UpdateTrackPictureLog(
Guid TrackId, Guid TrackId,
string PreviousOriginalPictureKey, string PreviousOriginalPictureKey,
string PreviousSmallPictureKey, string? PreviousSmallPictureKey,
string PreviousMediumPictureKey, string? PreviousMediumPictureKey,
string PreviousLargePictureKey); string? PreviousLargePictureKey);
} }
@@ -6,7 +6,6 @@ using OpenTelemetry.Exporter;
using OpenTelemetry.Metrics; using OpenTelemetry.Metrics;
using OpenTelemetry.Resources; using OpenTelemetry.Resources;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
using SixLabors.ImageSharp;
namespace Musify.Infrastructure.Observability namespace Musify.Infrastructure.Observability
{ {
@@ -1,5 +1,4 @@
using System; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable #nullable disable