From 4a78455947d53f1e712ebf8de164651dc26d26a6 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Sat, 21 Mar 2026 03:45:29 +0100 Subject: [PATCH] A lot of changes --- Application/Application.csproj | 4 + .../ConfigurationDependencyInjection.cs | 3 +- .../Configuration/PlayListConfiguration.cs | 11 ++- .../Configuration/TrackConfiguration.cs | 63 +++++++++++++ .../Contracts/Infrastructure/IDatabase.cs | 2 + Application/Events/ProcessTrackEvent.cs | 4 + Application/Events/RemoveFileEvent.cs | 4 +- Application/Events/UpdateTrackPictureEvent.cs | 16 ++++ .../CreatePlayListCommandHandler.cs | 7 +- .../DeletePlayListCommandHandler.cs | 44 ++++------ .../UpdatePlayListCommandHandler.cs | 76 ++++++---------- .../CreateTrack/CreateTrackCommand.cs | 14 +++ .../CreateTrack/CreateTrackCommandHandler.cs | 87 ++++++++++++++++++ Application/Tracks/Contracts/TrackResponse.cs | 20 +++++ Domain/Entities/PlayList.cs | 3 - Domain/Entities/Track.cs | 12 ++- Domain/Entities/User.cs | 2 +- Domain/Entities/UserHasTrack.cs | 24 +++++ .../Arguments/UpdateTrackPictureArguments.cs | 8 ++ .../Activities/UpdateTrackPictureActivity.cs | 53 +++++++++++ .../UpdatePlayListPictureConsumer.cs | 5 +- .../Consumers/UpdateTrackPictureConsumer.cs | 88 +++++++++++++++++++ Infrastructure/Persistence/Database.cs | 2 + 23 files changed, 456 insertions(+), 96 deletions(-) create mode 100644 Application/Configuration/TrackConfiguration.cs create mode 100644 Application/Events/ProcessTrackEvent.cs create mode 100644 Application/Events/UpdateTrackPictureEvent.cs create mode 100644 Application/Tracks/Commands/CreateTrack/CreateTrackCommand.cs create mode 100644 Application/Tracks/Commands/CreateTrack/CreateTrackCommandHandler.cs create mode 100644 Application/Tracks/Contracts/TrackResponse.cs create mode 100644 Domain/Entities/UserHasTrack.cs create mode 100644 Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs create mode 100644 Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs create mode 100644 Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs diff --git a/Application/Application.csproj b/Application/Application.csproj index cde91d6..0a86904 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/Application/Configuration/ConfigurationDependencyInjection.cs b/Application/Configuration/ConfigurationDependencyInjection.cs index be5b37d..b82a141 100644 --- a/Application/Configuration/ConfigurationDependencyInjection.cs +++ b/Application/Configuration/ConfigurationDependencyInjection.cs @@ -10,7 +10,8 @@ namespace Musify.Application.Configuration { serviceDescriptors .AddValidatedConfiguration(configuration, ApplicationStorageConfiguration.SectionName) - .AddValidatedConfiguration(configuration, PlayListConfiguration.SectionName); + .AddValidatedConfiguration(configuration, PlayListConfiguration.SectionName) + .AddValidatedConfiguration(configuration, TrackConfiguration.SectionName); return serviceDescriptors; } diff --git a/Application/Configuration/PlayListConfiguration.cs b/Application/Configuration/PlayListConfiguration.cs index 18707cd..4effc04 100644 --- a/Application/Configuration/PlayListConfiguration.cs +++ b/Application/Configuration/PlayListConfiguration.cs @@ -7,14 +7,17 @@ namespace Musify.Application.Configuration public const string SectionName = "PlayListConfiguration"; [Required] - public Routes Routes { get; set; } = new Routes(); + public PlayListRoutes Routes { get; set; } = new PlayListRoutes(); [Required] - public PicturesSizes PicturesSizes { get; set; } = new PicturesSizes(); + public PlayListPicturesSizes PicturesSizes { get; set; } = new PlayListPicturesSizes(); } - public class Routes + public class PlayListRoutes { + [Required] + public string ParentFolders { get; set; } = "PlayLists"; + [Required] public string OriginalPictures { get; set; } = "OriginalPictures"; @@ -37,7 +40,7 @@ namespace Musify.Application.Configuration public string PresetLargePicture { get; set; } = "PresetLargePicture.webp"; } - public class PicturesSizes + public class PlayListPicturesSizes { [Range(1, 1024)] public int SmallPictureWidth { get; set; } = 128; diff --git a/Application/Configuration/TrackConfiguration.cs b/Application/Configuration/TrackConfiguration.cs new file mode 100644 index 0000000..298a66a --- /dev/null +++ b/Application/Configuration/TrackConfiguration.cs @@ -0,0 +1,63 @@ +using System.ComponentModel.DataAnnotations; + +namespace Musify.Application.Configuration +{ + public class TrackConfiguration + { + public const string SectionName = "TrackConfiguration"; + + [Required] + public TrackRoutes Routes { get; set; } = new TrackRoutes(); + + [Required] + public TrackPicturesSizes PicturesSizes { get; set; } = new TrackPicturesSizes(); + } + + public class TrackRoutes + { + [Required] + public string ParentFolder { get; set; } = "Tracks"; + + [Required] + public string OriginalPictures { get; set; } = "OriginalTracks"; + + [Required] + public string SmallPictures { get; set; } = "SmallTracks"; + + [Required] + public string MediumPictures { get; set; } = "MediumTracks"; + + [Required] + public string LargePictures { get; set; } = "LargeTracks"; + + [Required] + public string PresetSmallPicture { get; set; } = "PresetSmallPicture.webp"; + + [Required] + public string PresetMediumPicture { get; set; } = "PresetMediumPicture.webp"; + + [Required] + public string PresetLargePicture { get; set; } = "PresetLargePicture.webp"; + } + + public class TrackPicturesSizes + { + [Range(1, 1024)] + public int SmallPictureWidth { get; set; } = 128; + + [Range(1, 1024)] + public int SmallPictureHeight { get; set; } = 128; + + [Range(1, 1024)] + public int MediumPictureWidth { get; private set; } = 256; + + [Range(1, 1024)] + public int MediumPictureHeight { get; private set; } = 256; + + [Range(1, 1024)] + public int LargePictureWidth { get; private set; } = 512; + + [Range(1, 1024)] + public int LargePictureHeight { get; private set; } = 512; + } +} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IDatabase.cs b/Application/Contracts/Infrastructure/IDatabase.cs index ab56211..9f49cf4 100644 --- a/Application/Contracts/Infrastructure/IDatabase.cs +++ b/Application/Contracts/Infrastructure/IDatabase.cs @@ -11,6 +11,8 @@ namespace Musify.Application.Contracts.Infrastructure DbSet Tracks { get; } + DbSet UserHasTracks { get; } + DbSet PlayListHasTracks { get; } DbSet Uploads { get; } diff --git a/Application/Events/ProcessTrackEvent.cs b/Application/Events/ProcessTrackEvent.cs new file mode 100644 index 0000000..30899f2 --- /dev/null +++ b/Application/Events/ProcessTrackEvent.cs @@ -0,0 +1,4 @@ +namespace Musify.Application.Events +{ + public record ProcessTrackEvent(); +} \ No newline at end of file diff --git a/Application/Events/RemoveFileEvent.cs b/Application/Events/RemoveFileEvent.cs index 88cbd0e..76fc108 100644 --- a/Application/Events/RemoveFileEvent.cs +++ b/Application/Events/RemoveFileEvent.cs @@ -1,6 +1,4 @@ -using Musify.Application.Contracts.Application; - -namespace Musify.Application.Events +namespace Musify.Application.Events { public record RemoveFileEvent( string BucketName, diff --git a/Application/Events/UpdateTrackPictureEvent.cs b/Application/Events/UpdateTrackPictureEvent.cs new file mode 100644 index 0000000..ada8d60 --- /dev/null +++ b/Application/Events/UpdateTrackPictureEvent.cs @@ -0,0 +1,16 @@ +namespace Musify.Application.Events +{ + public record UpdateTrackPictureEvent( + Guid TrackId, + string BucketName, + string OriginalPictureKeyName, + string SmallPictureKeyName, + int SmallPictureWidth, + int SmallPictureHeight, + string MediumPictureKeyName, + int MediumPictureWidth, + int MediumPictureHeight, + string LargePictureKeyName, + int LargePictureWidth, + int LargePictureHeight); +} \ No newline at end of file diff --git a/Application/PlayLists/Commands/CreatePlayList/CreatePlayListCommandHandler.cs b/Application/PlayLists/Commands/CreatePlayList/CreatePlayListCommandHandler.cs index bea11d8..3d4a6f2 100644 --- a/Application/PlayLists/Commands/CreatePlayList/CreatePlayListCommandHandler.cs +++ b/Application/PlayLists/Commands/CreatePlayList/CreatePlayListCommandHandler.cs @@ -10,9 +10,8 @@ using Musify.Domain.Entities; namespace Musify.Application.PlayLists.Commands.CreatePlayList { - public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler, - ILogger logger, ApplicationStorageConfiguration storageConfiguration, - PlayListConfiguration playListConfiguration, IPictureHandler pictureHandler) + public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler, IPictureHandler pictureHandler, + ILogger logger, ApplicationStorageConfiguration storageConfiguration, PlayListConfiguration playListConfiguration) : IRequestHandler> > { public async Task> Handle(CreatePlayListCommand request, CancellationToken cancellationToken) @@ -55,8 +54,6 @@ namespace Musify.Application.PlayLists.Commands.CreatePlayList playList.Id, string.Join("; ", uploadResult.Errors)); return Result.Error($"Failed to upload picture for PlayList with Id {playList.Id} to storage."); } - - playList.OriginalPictureKeyName = pictureName; logger.LogInformation("Playlist image uploaded successfully for PlayList with Id={PlayListId}.", playList.Id); var smallPictureKeyName = Path.Combine( diff --git a/Application/PlayLists/Commands/DeletePlayList/DeletePlayListCommandHandler.cs b/Application/PlayLists/Commands/DeletePlayList/DeletePlayListCommandHandler.cs index 8853bdd..fc03dd8 100644 --- a/Application/PlayLists/Commands/DeletePlayList/DeletePlayListCommandHandler.cs +++ b/Application/PlayLists/Commands/DeletePlayList/DeletePlayListCommandHandler.cs @@ -27,36 +27,28 @@ namespace Musify.Application.PlayLists.Commands.DeletePlayList return Result.Unauthorized(); } - if (!string.IsNullOrWhiteSpace(playList.OriginalPictureKeyName)) + var keysToRemove = new List(); + + if (playList.SmallPictureKeyName != playListConfiguration.Routes.PresetSmallPicture) { - var keysToRemove = new List - { - Path.Join(playListConfiguration.Routes.OriginalPictures, playList.OriginalPictureKeyName) - }; + keysToRemove.Add(playList.SmallPictureKeyName); + } + if (playList.MediumPictureKeyName != playListConfiguration.Routes.PresetMediumPicture) + { + keysToRemove.Add(playList.MediumPictureKeyName); + } + if (playList.LargePictureKeyName != playListConfiguration.Routes.PresetLargePicture) + { + keysToRemove.Add(playList.LargePictureKeyName); + } - if (playList.SmallPictureKeyName != playListConfiguration.Routes.PresetSmallPicture) - { - keysToRemove.Add(playList.SmallPictureKeyName); - } + foreach (var key in keysToRemove) + { + var publishResult = await TryPublishRemoveFileEventAsync(playList.Id, key, cancellationToken); - if (playList.MediumPictureKeyName != playListConfiguration.Routes.PresetMediumPicture) + if (!publishResult.IsSuccess) { - keysToRemove.Add(playList.MediumPictureKeyName); - } - - if (playList.LargePictureKeyName != playListConfiguration.Routes.PresetLargePicture) - { - keysToRemove.Add(playList.LargePictureKeyName); - } - - foreach (var key in keysToRemove) - { - var publishResult = await TryPublishRemoveFileEventAsync(playList.Id, key, cancellationToken); - - if (!publishResult.IsSuccess) - { - return publishResult; - } + return publishResult; } } diff --git a/Application/PlayLists/Commands/UpdatePlayList/UpdatePlayListCommandHandler.cs b/Application/PlayLists/Commands/UpdatePlayList/UpdatePlayListCommandHandler.cs index 3429cfd..e58e352 100644 --- a/Application/PlayLists/Commands/UpdatePlayList/UpdatePlayListCommandHandler.cs +++ b/Application/PlayLists/Commands/UpdatePlayList/UpdatePlayListCommandHandler.cs @@ -47,68 +47,46 @@ namespace Musify.Application.PlayLists.Commands.UpdatePlayList return Result.Success(PlayListResponse.FromEntity(playList)); } - if (!string.IsNullOrWhiteSpace(playList.OriginalPictureKeyName)) + if (playList.SmallPictureKeyName != playListConfiguration.Routes.PresetSmallPicture) { - var originalImageStorageKey = Path.Join(playListConfiguration.Routes.OriginalPictures, playList.OriginalPictureKeyName); - try { await eventBus.PublishAsync(new RemoveFileEvent( - storageConfiguration.BucketName, - originalImageStorageKey), cancellationToken); + storageConfiguration.BucketName, + playList.SmallPictureKeyName), cancellationToken); } catch (Exception exception) { - logger.LogError(exception, "Failed to publish remove file event for original picture of PlayList id={PlayListId}.", playList.Id); + logger.LogError(exception, "Failed to publish remove file event for small picture of PlayList id={PlayListId}.", playList.Id); return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); } - - logger.LogInformation("Published event to remove old picture with key name {KeyName} from bucket {BucketName}", - originalImageStorageKey, storageConfiguration.BucketName); - - if (playList.SmallPictureKeyName != playListConfiguration.Routes.PresetSmallPicture) + } + if (playList.MediumPictureKeyName != playListConfiguration.Routes.PresetMediumPicture) + { + try { - try - { - await eventBus.PublishAsync(new RemoveFileEvent( - storageConfiguration.BucketName, - playList.SmallPictureKeyName), cancellationToken); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to publish remove file event for small picture of PlayList id={PlayListId}.", playList.Id); - return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); - } + await eventBus.PublishAsync(new RemoveFileEvent( + storageConfiguration.BucketName, + playList.MediumPictureKeyName), cancellationToken); } - - if (playList.MediumPictureKeyName != playListConfiguration.Routes.PresetMediumPicture) + catch (Exception exception) { - try - { - await eventBus.PublishAsync(new RemoveFileEvent( - storageConfiguration.BucketName, - playList.MediumPictureKeyName), cancellationToken); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to publish remove file event for medium picture of PlayList id={PlayListId}.", playList.Id); - return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); - } + logger.LogError(exception, "Failed to publish remove file event for medium picture of PlayList id={PlayListId}.", playList.Id); + return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); } - - if (playList.LargePictureKeyName != playListConfiguration.Routes.PresetLargePicture) + } + if (playList.LargePictureKeyName != playListConfiguration.Routes.PresetLargePicture) + { + try { - try - { - await eventBus.PublishAsync(new RemoveFileEvent( - storageConfiguration.BucketName, - playList.LargePictureKeyName), cancellationToken); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to publish remove file event for large picture of PlayList id={PlayListId}.", playList.Id); - return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); - } + await eventBus.PublishAsync(new RemoveFileEvent( + storageConfiguration.BucketName, + playList.LargePictureKeyName), cancellationToken); + } + catch (Exception exception) + { + logger.LogError(exception, "Failed to publish remove file event for large picture of PlayList id={PlayListId}.", playList.Id); + return Result.Error($"Failed to publish remove file event for PlayList with id {playList.Id}."); } } @@ -130,8 +108,6 @@ namespace Musify.Application.PlayLists.Commands.UpdatePlayList return Result.Error($"Failed to upload picture for PlayList with Id {playList.Id} to storage."); } - playList.OriginalPictureKeyName = pictureName; - var smallPictureKeyName = Path.Combine( playListConfiguration.Routes.SmallPictures, $"{playList.Id}.{pictureHandler.FileExtension}"); diff --git a/Application/Tracks/Commands/CreateTrack/CreateTrackCommand.cs b/Application/Tracks/Commands/CreateTrack/CreateTrackCommand.cs new file mode 100644 index 0000000..c10007c --- /dev/null +++ b/Application/Tracks/Commands/CreateTrack/CreateTrackCommand.cs @@ -0,0 +1,14 @@ +using Ardalis.Result; +using DispatchR.Abstractions.Send; +using Musify.Application.Contracts.Application; +using Musify.Application.Tracks.Contracts; + +namespace Musify.Application.Tracks.Commands.CreateTrack +{ + public record CreateTrackCommand( + Guid UserId, + string Title, + IFileData Picture, + IFileData Audio) + : IRequest >>; +} \ No newline at end of file diff --git a/Application/Tracks/Commands/CreateTrack/CreateTrackCommandHandler.cs b/Application/Tracks/Commands/CreateTrack/CreateTrackCommandHandler.cs new file mode 100644 index 0000000..2b0a745 --- /dev/null +++ b/Application/Tracks/Commands/CreateTrack/CreateTrackCommandHandler.cs @@ -0,0 +1,87 @@ +using Ardalis.Result; +using DispatchR.Abstractions.Send; +using Microsoft.Extensions.Logging; +using Musify.Application.Configuration; +using Musify.Application.Contracts.Infrastructure; +using Musify.Application.Events; +using Musify.Application.Tracks.Contracts; +using Musify.Domain.Entities; + +namespace Musify.Application.Tracks.Commands.CreateTrack +{ + public class CreateTrackCommandHandler(IDatabase database, IPictureHandler pictureHandler, IEventBus eventBus, IStorageHandler storageHandler, + ILogger logger, ApplicationStorageConfiguration storageConfiguration, TrackConfiguration trackConfiguration) + : IRequestHandler >> + { + public async Task> Handle(CreateTrackCommand request, CancellationToken cancellationToken) + { + var userExists = await database.Users.FindAsync(request.UserId, cancellationToken); + + if (userExists is null) + { + logger.LogWarning("User with id {UserId} not found", request.UserId); + return Result.NotFound($"User with id {request.UserId} not found"); + } + + var originalPictureKeyName = $"{Guid.NewGuid() }.{request.Picture.FileType}"; + var track = new Track + { + Title = request.Title, + NormalizedTitle = request.Title.Trim().ToUpperInvariant(), + SmallPictureKeyName = trackConfiguration.Routes.PresetSmallPicture, + MediumPictureKeyName = trackConfiguration.Routes.PresetMediumPicture, + LargePictureKeyName = trackConfiguration.Routes.PresetLargePicture, + }; + + var pictureUploadResult = await storageHandler.UploadFileAsync( + request.Picture.FileStream, + request.Picture.ContentType, + storageConfiguration.BucketName, + originalPictureKeyName, + cancellationToken); + + if (!pictureUploadResult.IsSuccess) + { + logger.LogError("Failed to upload picture for track {TrackTitle}", request.Title); + return Result.Error($"Failed to upload picture for track {request.Title}"); + } + + var smallPictureKeyName = Path.Combine( + trackConfiguration.Routes.SmallPictures, + $"{track.Id}.{pictureHandler.FileExtension}"); + var mediumPictureKeyName = Path.Combine( + trackConfiguration.Routes.MediumPictures, + $"{track.Id}.{pictureHandler.FileExtension}"); + var largePictureKeyName = Path.Combine( + trackConfiguration.Routes.LargePictures, + $"{track.Id}.{pictureHandler.FileExtension}"); + + try + { + await eventBus.PublishAsync(new UpdatePlayListPictureEvent( + track.Id, + storageConfiguration.BucketName, + originalPictureKeyName, + smallPictureKeyName, + trackConfiguration.PicturesSizes.SmallPictureWidth, + trackConfiguration.PicturesSizes.SmallPictureHeight, + mediumPictureKeyName, + trackConfiguration.PicturesSizes.MediumPictureWidth, + trackConfiguration.PicturesSizes.MediumPictureHeight, + largePictureKeyName, + trackConfiguration.PicturesSizes.LargePictureWidth, + trackConfiguration.PicturesSizes.LargePictureHeight), cancellationToken); + } + catch (Exception exception) + { + logger.LogError(exception, "Failed to publish resize picture event for Track with id={TrackId}.", track.Id); + return Result.Error($"Failed to publish resize picture event for Track with id {track.Id}."); + } + + await database.Tracks.AddAsync(track, cancellationToken); + await database.SaveChangesAsync(cancellationToken); + + return Result.Success(); + } + } +} diff --git a/Application/Tracks/Contracts/TrackResponse.cs b/Application/Tracks/Contracts/TrackResponse.cs new file mode 100644 index 0000000..8a0a67e --- /dev/null +++ b/Application/Tracks/Contracts/TrackResponse.cs @@ -0,0 +1,20 @@ +using Musify.Domain.Entities; + +namespace Musify.Application.Tracks.Contracts +{ + public record TrackResponse( + Guid Id, + string Title, + DateTime CreatedDate, + DateTime UpdatedDate) + { + public static TrackResponse FromEntity(Track track) + { + return new TrackResponse( + track.Id, + track.Title, + track.CreatedDate, + track.UpdatedDate); + } + } +} \ No newline at end of file diff --git a/Domain/Entities/PlayList.cs b/Domain/Entities/PlayList.cs index 7975558..44d751f 100644 --- a/Domain/Entities/PlayList.cs +++ b/Domain/Entities/PlayList.cs @@ -25,9 +25,6 @@ namespace Musify.Domain.Entities [MaxLength(254)] public required string Description { get; set; } = "No description was provided."; - [MaxLength(64)] - public string? OriginalPictureKeyName { get; set; } - [Required] [MaxLength(64)] public required string SmallPictureKeyName { get; set; } diff --git a/Domain/Entities/Track.cs b/Domain/Entities/Track.cs index 6fae133..8986fdd 100644 --- a/Domain/Entities/Track.cs +++ b/Domain/Entities/Track.cs @@ -18,7 +18,15 @@ namespace Musify.Domain.Entities [Required] [MaxLength(64)] - public required string ImageName { get; set; } + public required string SmallPictureKeyName { get; set; } + + [Required] + [MaxLength(64)] + public required string MediumPictureKeyName { get; set; } + + [Required] + [MaxLength(64)] + public required string LargePictureKeyName { get; set; } [Required] public DateTime CreatedDate { get; set; } = DateTime.UtcNow; @@ -26,7 +34,7 @@ namespace Musify.Domain.Entities [Required] public DateTime UpdatedDate { get; set; } = DateTime.UtcNow; - public ICollection Users { get; set; } = new List(); + public ICollection UserTracks { get; set; } = new List(); public ICollection PlayListTracks { get; set; } = new List(); } diff --git a/Domain/Entities/User.cs b/Domain/Entities/User.cs index 08784e3..2703c2a 100644 --- a/Domain/Entities/User.cs +++ b/Domain/Entities/User.cs @@ -15,7 +15,7 @@ namespace Musify.Domain.Entities [Required] public DateTime UpdatedAt { get; set; } = DateTime.Now; - public ICollection Tracks { get; set; } = new List(); + public ICollection UserTracks { get; set; } = new List(); public ICollection PlayLists { get; set; } = new List(); } diff --git a/Domain/Entities/UserHasTrack.cs b/Domain/Entities/UserHasTrack.cs new file mode 100644 index 0000000..fbf0d07 --- /dev/null +++ b/Domain/Entities/UserHasTrack.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Musify.Domain.Entities +{ +#pragma warning disable CS8618 + [Table("UserHasTrack")] + public class UserHasTrack + { + public Guid Id { get; set; } = Guid.NewGuid(); + + [Required] + public required Guid UserId { get; set; } + + [Required] + public required Guid TrackId { get; set; } + + [ForeignKey(nameof(UserId)) ] + public User User { get; set; } + + [ForeignKey(nameof(TrackId)) ] + public Track Track { get; set; } + } +} \ No newline at end of file diff --git a/Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs b/Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs new file mode 100644 index 0000000..be1106f --- /dev/null +++ b/Infrastructure/Messaging/Activities/Arguments/UpdateTrackPictureArguments.cs @@ -0,0 +1,8 @@ +namespace Musify.Infrastructure.Messaging.Activities.Arguments +{ + public record UpdateTrackPictureArguments( + Guid TrackId, + string SmallPictureKeyName, + string MediumPictureKeyName, + string LargePictureKeyName); +} \ No newline at end of file diff --git a/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs b/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs new file mode 100644 index 0000000..63671de --- /dev/null +++ b/Infrastructure/Messaging/Activities/UpdateTrackPictureActivity.cs @@ -0,0 +1,53 @@ +using MassTransit; +using Musify.Application.Contracts.Infrastructure; +using Musify.Domain.Entities; +using Musify.Infrastructure.Messaging.Activities.Arguments; + +namespace Musify.Infrastructure.Messaging.Activities +{ + public class UpdateTrackPictureActivity(IDatabase database, IProcessTrackingStore processTrackingStore) + : IExecuteActivity + { + public const string ExecuteEndpointName = "Update-Track-Picture"; + + public async Task Execute(ExecuteContext executeContext) + { + var processId = await processTrackingStore.GetOrCreateProcessAsync( + "RoutingSlip", + executeContext.CorrelationId ?? executeContext.TrackingNumber, + executeContext.ConversationId, + executeContext.MessageId, + executeContext.CancellationToken); + + var stepId = await processTrackingStore.StartStepAsync( + processId, + nameof(UpdateTrackPictureActivity), + ProcessStepComponentType.Activity, + 0, + executeContext.CancellationToken); + + try + { + var track = await database.Tracks.FindAsync(executeContext.Arguments.TrackId, executeContext.CancellationToken); + + if (track is null) + throw new Exception($"Track with id {executeContext.Arguments.TrackId} not found."); + + track.SmallPictureKeyName = executeContext.Arguments.SmallPictureKeyName; + track.MediumPictureKeyName = executeContext.Arguments.MediumPictureKeyName; + track.LargePictureKeyName = executeContext.Arguments.LargePictureKeyName; + + database.Tracks.Update(track); + await database.SaveChangesAsync(executeContext.CancellationToken); + + await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken); + return executeContext.Completed(); + } + catch (Exception exception) + { + await processTrackingStore.FailStepAsync(processId, stepId, exception.Message, executeContext.CancellationToken); + throw; + } + } + } +} \ No newline at end of file diff --git a/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs b/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs index ccf6b9a..e542bc0 100644 --- a/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs +++ b/Infrastructure/Messaging/Consumers/UpdatePlayListPictureConsumer.cs @@ -44,7 +44,10 @@ namespace Musify.Infrastructure.Messaging.Consumers routingSlipBuilder.AddActivity( "DeleteOriginal", BuildExecuteUri(RemoveFilesActivity.ExecuteEndpointName), - new RemoveFileArguments(consumeContext.Message.BucketName, consumeContext.Message.OriginalPictureKeyName)); + new RemoveFileArguments( + consumeContext.Message.BucketName, + consumeContext.Message.OriginalPictureKeyName) + ); routingSlipBuilder.AddActivity( "UpdatePlayListPicture", diff --git a/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs b/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs new file mode 100644 index 0000000..00e0911 --- /dev/null +++ b/Infrastructure/Messaging/Consumers/UpdateTrackPictureConsumer.cs @@ -0,0 +1,88 @@ +using MassTransit; +using Musify.Application.Events; +using Musify.Infrastructure.Messaging.Activities; +using Musify.Infrastructure.Messaging.Activities.Arguments; + +namespace Musify.Infrastructure.Messaging.Consumers +{ + public class UpdateTrackPictureConsumer(IBus bus) + : IConsumer + { + public const string QueueName = "update-track-picture"; + + public async Task Consume(ConsumeContext consumeContext) + { + var routingSlipBuilder = new RoutingSlipBuilder(NewId.NextGuid()); + + AddResizeActivity( + routingSlipBuilder, + "ResizeSmall", + consumeContext.Message.SmallPictureKeyName, + consumeContext.Message.SmallPictureWidth, + consumeContext.Message.SmallPictureHeight, + consumeContext.Message.OriginalPictureKeyName, + consumeContext.Message.BucketName); + + AddResizeActivity( + routingSlipBuilder, + "ResizeMedium", + consumeContext.Message.SmallPictureKeyName, + consumeContext.Message.MediumPictureWidth, + consumeContext.Message.MediumPictureHeight, + consumeContext.Message.OriginalPictureKeyName, + consumeContext.Message.BucketName); + + AddResizeActivity( + routingSlipBuilder, + "ResizeMedium", + consumeContext.Message.SmallPictureKeyName, + consumeContext.Message.LargePictureWidth, + consumeContext.Message.LargePictureHeight, + consumeContext.Message.OriginalPictureKeyName, + consumeContext.Message.BucketName); + + routingSlipBuilder.AddActivity( + "DeleteOriginal", + BuildExecuteUri(RemoveFilesActivity.ExecuteEndpointName), + new RemoveFileArguments( + consumeContext.Message.BucketName, + consumeContext.Message.OriginalPictureKeyName) + ); + + routingSlipBuilder.AddActivity( + "UpdateTrackPictureActivity", + BuildExecuteUri(UpdateTrackPictureActivity.ExecuteEndpointName), + new UpdateTrackPictureArguments( + consumeContext.Message.TrackId, + consumeContext.Message.SmallPictureKeyName, + consumeContext.Message.MediumPictureKeyName, + consumeContext.Message.LargePictureKeyName)); + + var routingSlip = routingSlipBuilder.Build(); + await bus.Execute(routingSlip); + } + + void AddResizeActivity( + RoutingSlipBuilder routingSlipBuilder, + string activityName, + string destinationKeyName, + int width, + int height, + string originalPictureKeyName, + string bucketName) + { + routingSlipBuilder.AddActivity( + activityName, + BuildExecuteUri(ResizePictureActivity.ExecuteEndpointName), + new ResizePictureArgument( + bucketName, + originalPictureKeyName, + bucketName, + destinationKeyName, + width, + height)); + } + + static Uri BuildExecuteUri(string endpointName) => new($"queue:{endpointName}_execute"); + } +} \ No newline at end of file diff --git a/Infrastructure/Persistence/Database.cs b/Infrastructure/Persistence/Database.cs index 5676339..993fe77 100644 --- a/Infrastructure/Persistence/Database.cs +++ b/Infrastructure/Persistence/Database.cs @@ -19,6 +19,8 @@ namespace Musify.Infrastructure.Persistence public DbSet PlayLists => Set(); + public DbSet UserHasTracks => Set(); + public DbSet PlayListHasTracks => Set(); public DbSet Uploads => Set();