Safe code and changes
This commit is contained in:
@@ -62,13 +62,31 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
{
|
||||
var publishResult = await PublishPictureUpdateEventAsync(playList, cancellationToken);
|
||||
if (!publishResult.IsSuccess)
|
||||
{
|
||||
await TryRollbackOnPublishFailureAsync(playList, cancellationToken);
|
||||
return publishResult;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Created playlist {PlayListId} for user {UserId}", playList.Id, request.UserId);
|
||||
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)
|
||||
{
|
||||
var pictureName = Guid.NewGuid() + picture.FileType;
|
||||
@@ -123,6 +141,9 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
if (string.IsNullOrEmpty(playList.OriginalPictureName))
|
||||
return;
|
||||
|
||||
if (playList.OriginalPictureName == playListConfiguration.Routes.PresetOriginalPicture)
|
||||
return;
|
||||
|
||||
var originalPicturePath = playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName);
|
||||
|
||||
try
|
||||
|
||||
@@ -28,25 +28,73 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
return Result.Unauthorized();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(playList.OriginalPictureName))
|
||||
await PublishRemoveFileEventAsync(playList.Id,
|
||||
playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName), cancellationToken);
|
||||
if (playList.SmallPictureName != playListConfiguration.Routes.PresetSmallPicture)
|
||||
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);
|
||||
var originalPictureName = playList.OriginalPictureName;
|
||||
var smallPictureName = playList.SmallPictureName;
|
||||
var mediumPictureName = playList.MediumPictureName;
|
||||
var largePictureName = playList.LargePictureName;
|
||||
|
||||
database.PlayLists.Remove(playList);
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
var originalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName);
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -16,14 +16,16 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
{
|
||||
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
|
||||
var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
|
||||
var normalizedName = request.Name?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
|
||||
var playListsQuery = database.PlayLists
|
||||
.AsNoTracking()
|
||||
.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));
|
||||
}
|
||||
|
||||
var totalCount = await playListsQuery.CountAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
PlayListConfiguration playListConfiguration)
|
||||
: 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)
|
||||
{
|
||||
var playListEntity = await database.PlayLists.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken);
|
||||
@@ -32,11 +35,14 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
return Result.Unauthorized();
|
||||
}
|
||||
|
||||
var previousState = CapturePreviousState(playListEntity);
|
||||
|
||||
UpdateName(playListEntity, request.NewName);
|
||||
UpdateDescription(playListEntity, request.NewDescription);
|
||||
var updatePictureResult = await UpdatePicture(playListEntity, request.NewPicture, cancellationToken);
|
||||
if (!updatePictureResult.IsSuccess)
|
||||
return updatePictureResult;
|
||||
|
||||
var pictureOutcome = await UpdatePicture(playListEntity, request.NewPicture, cancellationToken);
|
||||
if (!pictureOutcome.Result.IsSuccess)
|
||||
return pictureOutcome.Result;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -45,20 +51,24 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
}
|
||||
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);
|
||||
return Result.Error($"Failed to update playlist {playListEntity.Id}");
|
||||
}
|
||||
|
||||
try
|
||||
if (pictureOutcome.PictureUpdated)
|
||||
{
|
||||
await PublishPlayListPictureEvent(playListEntity, cancellationToken);
|
||||
await RemoveOldPictures(playListEntity, cancellationToken);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError(exception, "Failed to publish playlist picture update event or remove old pictures for playlist {PlayListId}", playListEntity.Id);
|
||||
return Result.Error($"Failed to update playlist {playListEntity.Id}");
|
||||
var publishResult = await PublishPlayListPictureEvent(playListEntity, cancellationToken);
|
||||
if (!publishResult.IsSuccess)
|
||||
{
|
||||
await TryRollbackAfterPublishFailureAsync(
|
||||
playListEntity,
|
||||
previousState,
|
||||
pictureOutcome.UploadedPictureKey,
|
||||
cancellationToken);
|
||||
return Result.Error($"Failed to update playlist {playListEntity.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Updated playlist {PlayListId}", playListEntity.Id);
|
||||
@@ -67,26 +77,34 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
|
||||
void UpdateName(PlayList playList, string? newName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(newName))
|
||||
{
|
||||
playList.Name = newName;
|
||||
playList.NormalizedName = newName.Trim().ToUpperInvariant();
|
||||
}
|
||||
if (newName is null)
|
||||
return;
|
||||
|
||||
playList.Name = newName;
|
||||
playList.NormalizedName = newName.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
void UpdateDescription(PlayList playList, string? newDescription)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(newDescription))
|
||||
playList.Description = newDescription;
|
||||
if (newDescription is null)
|
||||
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)
|
||||
return Result.Success();
|
||||
return new UpdatePictureOutcome(Result.Success(), false, null);
|
||||
|
||||
var pictureName = Guid.NewGuid() + newPicture.FileType;
|
||||
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName);
|
||||
var pictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(pictureName);
|
||||
try
|
||||
{
|
||||
await storageHandler.UploadFileAsync(
|
||||
@@ -98,11 +116,11 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
}
|
||||
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;
|
||||
return Result.Success();
|
||||
return new UpdatePictureOutcome(Result.Success(), true, pictureKey);
|
||||
}
|
||||
|
||||
async Task<Result> PublishPlayListPictureEvent(PlayList playList, CancellationToken cancellationToken)
|
||||
@@ -112,7 +130,7 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
await eventBus.PublishAsync(new UpdatePlayListPictureEvent(
|
||||
playList.Id,
|
||||
storageConfiguration.Bucket,
|
||||
Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, playList.OriginalPictureName),
|
||||
playListConfiguration.Routes.BuildOriginalPicturePath(playList.OriginalPictureName),
|
||||
new ImageSize(
|
||||
playListConfiguration.Routes.SmallPicturesPath,
|
||||
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)
|
||||
{
|
||||
await storageHandler.RemoveFileAsync(
|
||||
@@ -163,20 +162,37 @@ namespace Musify.Application.PlayLists.Handlers
|
||||
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
|
||||
{
|
||||
await eventBus.PublishAsync(new RemoveFileEvent(
|
||||
storageConfiguration.Bucket,
|
||||
key), cancellationToken);
|
||||
return Result.NoContent();
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError(exception, "Failed to publish remove file event for key {Key}", key);
|
||||
return Result.Error($"Failed to publish remove file event for {key}");
|
||||
logger.LogError(exception, "Failed to rollback playlist state after publish failure for playlist {PlayListId}", playList.Id);
|
||||
}
|
||||
|
||||
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(
|
||||
Guid UserId,
|
||||
string Name = "",
|
||||
string? Name = null,
|
||||
int PageNumber = 1,
|
||||
int PageSize = 10)
|
||||
: IQuery<Result<PaginatedResponse<PlayListResponse>> >;
|
||||
|
||||
@@ -34,13 +34,35 @@ namespace Musify.Application.Tracks.Handler
|
||||
var fullPictureKey = trackConfiguration.Routes.BuildOriginalPicturePath(trackEntity.OriginalPictureName);
|
||||
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);
|
||||
await PublishTranscodeEvent(trackEntity, cancellationToken);
|
||||
try
|
||||
{
|
||||
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);
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
var publishPictureResult = await PublishUpdateEvent(trackEntity, 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));
|
||||
}
|
||||
@@ -91,7 +113,7 @@ namespace Musify.Application.Tracks.Handler
|
||||
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
|
||||
{
|
||||
@@ -111,14 +133,16 @@ namespace Musify.Application.Tracks.Handler
|
||||
trackConfiguration.Routes.LargePicturesPath,
|
||||
trackConfiguration.PicturesSizes.LargePictureWidth,
|
||||
trackConfiguration.PicturesSizes.LargePictureHeight)), cancellationToken);
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
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());
|
||||
|
||||
@@ -130,10 +154,38 @@ namespace Musify.Application.Tracks.Handler
|
||||
trackConfiguration.Routes.BuildOriginalAudioPath(track.OriginalAudioName),
|
||||
storageConfiguration.Bucket,
|
||||
destinationFolderAudio), cancellationToken);
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
return Result.Unauthorized();
|
||||
@@ -37,13 +40,46 @@ namespace Musify.Application.Tracks.Handler
|
||||
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.AudioTranscodeProcessingStatus = ProcessingStatus.Processing;
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await eventBus.PublishAsync(
|
||||
new DeleteTrackEvent(track.Id, request.UserId),
|
||||
cancellationToken);
|
||||
try
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -16,15 +16,17 @@ namespace Musify.Application.Tracks.Handler
|
||||
{
|
||||
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
|
||||
var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
|
||||
var normalizedName = request.Name?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
|
||||
var tracksQuery = database.UserHasTracks
|
||||
.AsNoTracking()
|
||||
.Include(ut => ut.Track)
|
||||
.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));
|
||||
}
|
||||
|
||||
var totalCount = await tracksQuery.CountAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -17,15 +17,16 @@ namespace Musify.Application.Tracks.Handler
|
||||
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
|
||||
var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
|
||||
|
||||
var totalCount = await database.PlayLists.CountAsync(cancellationToken);
|
||||
var pagedPlayLists = await database.UserHasTracks
|
||||
var tracksQuery = database.UserHasTracks
|
||||
.AsNoTracking()
|
||||
.OrderBy(t => t.Id)
|
||||
.Include(ut => ut.Track)
|
||||
.Select(t => TrackResponse.FromEntity(t.Track))
|
||||
.ToPagedListAsync(pageNumber, pageSize, totalCount, cancellationToken);
|
||||
.Select(t => TrackResponse.FromEntity(t.Track));
|
||||
|
||||
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(
|
||||
Guid UserId,
|
||||
string Name = "",
|
||||
string? Name = null,
|
||||
int PageNumber = 1,
|
||||
int PageSize = 10)
|
||||
: IQuery<Result<PaginatedResponse<TrackResponse> > >;
|
||||
|
||||
@@ -21,8 +21,17 @@ namespace Musify.Application.Users.Handlers
|
||||
}
|
||||
|
||||
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);
|
||||
return Result.Created(UserResponse.FromEntity(newUser));
|
||||
|
||||
@@ -6,7 +6,6 @@ using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Application.Users.Queries;
|
||||
using Musify.Application.Users.Responses;
|
||||
using X.PagedList.EF;
|
||||
using X.PagedList.Extensions;
|
||||
|
||||
namespace Musify.Application.Users.Handlers
|
||||
{
|
||||
@@ -17,12 +16,12 @@ namespace Musify.Application.Users.Handlers
|
||||
{
|
||||
var pageNumber = request.PageNumber < 1 ? 1 : request.PageNumber;
|
||||
var pageSize = request.PageSize < 1 ? 10 : request.PageSize;
|
||||
var first = (pageNumber - 1) * pageSize;
|
||||
|
||||
var user = database.Users
|
||||
.AsNoTracking()
|
||||
.AsQueryable();
|
||||
if (!string.IsNullOrEmpty(request.UsernameSearch))
|
||||
|
||||
if (request.UsernameSearch is not null)
|
||||
{
|
||||
var normalizedUsername = request.UsernameSearch.Trim().ToUpperInvariant();
|
||||
user = user.Where(u => u.NormalizedName.Contains(normalizedUsername));
|
||||
|
||||
@@ -5,6 +5,6 @@ using Musify.Application.Users.Responses;
|
||||
|
||||
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> >>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeMapping;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Infrastructure.MassTransit.Arguments;
|
||||
using Musify.Infrastructure.MassTransit.Logs;
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Infrastructure.MassTransit.Activities.Arguments;
|
||||
using Musify.Infrastructure.MassTransit.Activities.Logs;
|
||||
using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
|
||||
|
||||
namespace Musify.Infrastructure.MassTransit.Activities
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using MassTransit;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Domain.Entities;
|
||||
using Musify.Infrastructure.MassTransit.Activities.Arguments;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+144
@@ -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 Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
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
|
||||
{
|
||||
public static class MassTransitDependencyInjection
|
||||
public static partial class MassTransitDependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddMassTransitClient(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||
{
|
||||
serviceDescriptors
|
||||
.AddOptionsWithValidateOnStart<MassTransitConfiguration>()
|
||||
.Bind(configuration.GetRequiredSection(MassTransitConfiguration.SectionName))
|
||||
.ValidateDataAnnotations();
|
||||
|
||||
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||
serviceProvider.GetRequiredService<IOptions<MassTransitConfiguration>>().Value);
|
||||
RegisterValidatedOptions<MassTransitConfiguration>(
|
||||
serviceDescriptors,
|
||||
configuration,
|
||||
MassTransitConfiguration.SectionName);
|
||||
|
||||
serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>();
|
||||
serviceDescriptors.AddMassTransit(options =>
|
||||
@@ -43,124 +29,24 @@ namespace Musify.Infrastructure.MassTransit
|
||||
|
||||
public static IServiceCollection AddMassTransitConsumers(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||
{
|
||||
serviceDescriptors
|
||||
.AddOptionsWithValidateOnStart<MassTransitConfiguration>()
|
||||
.Bind(configuration.GetRequiredSection(MassTransitConfiguration.SectionName))
|
||||
.ValidateDataAnnotations();
|
||||
RegisterValidatedOptions<MassTransitConfiguration>(
|
||||
serviceDescriptors,
|
||||
configuration,
|
||||
MassTransitConfiguration.SectionName);
|
||||
|
||||
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||
serviceProvider.GetRequiredService<IOptions<MassTransitConfiguration>>().Value);
|
||||
RegisterValidatedOptions<WorkerConfiguration>(
|
||||
serviceDescriptors,
|
||||
configuration,
|
||||
WorkerConfiguration.SectionName);
|
||||
|
||||
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>();
|
||||
RegisterRoutingSlipBuilders(serviceDescriptors);
|
||||
|
||||
serviceDescriptors.AddMassTransit(options =>
|
||||
{
|
||||
options.AddConsumer<UpdatePlayListPictureConsumer>();
|
||||
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>();
|
||||
RegisterConsumersAndActivities(options);
|
||||
|
||||
options.UsingRabbitMq((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);
|
||||
} );
|
||||
ConfigureRabbitMqWorkerEndpoints(busRegistrationContext, busFactoryConfigurator));
|
||||
} );
|
||||
|
||||
return serviceDescriptors;
|
||||
@@ -176,37 +62,5 @@ namespace Musify.Infrastructure.MassTransit
|
||||
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(
|
||||
Guid TrackId,
|
||||
string PreviousOriginalPictureKey,
|
||||
string PreviousSmallPictureKey,
|
||||
string PreviousMediumPictureKey,
|
||||
string PreviousLargePictureKey);
|
||||
string? PreviousSmallPictureKey,
|
||||
string? PreviousMediumPictureKey,
|
||||
string? PreviousLargePictureKey);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using OpenTelemetry.Exporter;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace Musify.Infrastructure.Observability
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
||||
Reference in New Issue
Block a user