From 3e54b5db438ca15822c5955a46168c9d00cd5ce9 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Sun, 29 Mar 2026 17:15:39 +0200 Subject: [PATCH] Experimental changes --- .../Infrastructure/IAudioTranscoder.cs | 11 --- .../Infrastructure/IAudioTranscoderService.cs | 9 +++ .../Infrastructure/IKeycloakUserService.cs | 5 +- .../Infrastructure/IPictureHandler.cs | 9 --- .../Infrastructure/IPictureService.cs | 7 ++ .../Infrastructure/IStorageHandler.cs | 22 ----- .../Infrastructure/IStorageService.cs | 20 +++++ .../Handlers/CreatePlayListCommandHandler.cs | 14 ++-- .../Handlers/UpdatePlayListCommandHandler.cs | 25 +++--- .../Handler/CreateTrackCommandHandler.cs | 41 +++++----- .../Handler/DeleteTrackCommandHandler.cs | 2 +- .../Handlers/CreateUserCommandHandler.cs | 15 ++-- .../Users/Handlers/GetUserByIdQueryHandler.cs | 18 +++-- .../Handlers/GetUserByKeycloakQueryHandler.cs | 16 ++-- .../AudioTranscoderDependencyInjection.cs | 14 ---- .../ConfigurationDependencyInjection.cs | 40 --------- ...uration.cs => MassTransitConfiguration.cs} | 2 +- .../Identity/KeycloakDependencyInjection.cs | 30 ------- Infrastructure/Identity/KeycloakUserMapper.cs | 17 ---- .../InfrastructureDependencyInjection.cs | 50 ------------ .../Audio/TranscodeDashAudioActivity.cs | 12 +-- .../Files/DownloadFileFromBucketActivity.cs | 15 +--- .../Files/RemoveFileFromBucketActivity.cs | 13 +-- .../Files/TransferFilesToBucketActivity.cs | 15 +--- .../Files/UploadFileToBucketActivity.cs | 25 +----- .../Pictures/ResizePictureActivity.cs | 15 +--- .../MassTransitDependencyInjection.cs | 6 +- .../DatabaseDependencyInjection.cs | 10 +++ .../Pictures/PictureDependencyInjection.cs | 14 ---- .../AudioTranscoderService.cs} | 36 ++++----- .../KeycloakUserService.cs | 22 +++-- .../PictureService.cs} | 16 ++-- .../Services/ServicesDependencyInjection.cs | 81 +++++++++++++++++++ .../StorageService.cs} | 49 +++++------ .../Storage/StorageDependencyInjection.cs | 28 ------- 35 files changed, 279 insertions(+), 445 deletions(-) delete mode 100644 Application/Contracts/Infrastructure/IAudioTranscoder.cs create mode 100644 Application/Contracts/Infrastructure/IAudioTranscoderService.cs delete mode 100644 Application/Contracts/Infrastructure/IPictureHandler.cs create mode 100644 Application/Contracts/Infrastructure/IPictureService.cs delete mode 100644 Application/Contracts/Infrastructure/IStorageHandler.cs create mode 100644 Application/Contracts/Infrastructure/IStorageService.cs delete mode 100644 Infrastructure/Audio/AudioTranscoderDependencyInjection.cs delete mode 100644 Infrastructure/Configuration/ConfigurationDependencyInjection.cs rename Infrastructure/Configuration/{MessagingConfiguration.cs => MassTransitConfiguration.cs} (90%) delete mode 100644 Infrastructure/Identity/KeycloakDependencyInjection.cs delete mode 100644 Infrastructure/Identity/KeycloakUserMapper.cs delete mode 100644 Infrastructure/InfrastructureDependencyInjection.cs delete mode 100644 Infrastructure/Pictures/PictureDependencyInjection.cs rename Infrastructure/{Audio/AudioTranscoder.cs => Services/AudioTranscoderService.cs} (88%) rename Infrastructure/{Identity => Services}/KeycloakUserService.cs (68%) rename Infrastructure/{Pictures/PictureHandler.cs => Services/PictureService.cs} (74%) create mode 100644 Infrastructure/Services/ServicesDependencyInjection.cs rename Infrastructure/{Storage/StorageHandler.cs => Services/StorageService.cs} (74%) delete mode 100644 Infrastructure/Storage/StorageDependencyInjection.cs diff --git a/Application/Contracts/Infrastructure/IAudioTranscoder.cs b/Application/Contracts/Infrastructure/IAudioTranscoder.cs deleted file mode 100644 index f302d1b..0000000 --- a/Application/Contracts/Infrastructure/IAudioTranscoder.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ardalis.Result; - -namespace Musify.Application.Contracts.Infrastructure -{ - public interface IAudioTranscoder - { - Task TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken); - - Task> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IAudioTranscoderService.cs b/Application/Contracts/Infrastructure/IAudioTranscoderService.cs new file mode 100644 index 0000000..3c74cd5 --- /dev/null +++ b/Application/Contracts/Infrastructure/IAudioTranscoderService.cs @@ -0,0 +1,9 @@ +namespace Musify.Application.Contracts.Infrastructure +{ + public interface IAudioTranscoderService + { + Task TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken); + + Task IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IKeycloakUserService.cs b/Application/Contracts/Infrastructure/IKeycloakUserService.cs index f78c4a3..d11728b 100644 --- a/Application/Contracts/Infrastructure/IKeycloakUserService.cs +++ b/Application/Contracts/Infrastructure/IKeycloakUserService.cs @@ -1,11 +1,10 @@ -using Ardalis.Result; -using Musify.Application.Users.Responses; +using Musify.Application.Users.Responses; namespace Musify.Application.Contracts.Infrastructure { public interface IKeycloakUserService { - Task> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken); + Task GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken); Task> GetUsersAsync( CancellationToken cancellationToken, diff --git a/Application/Contracts/Infrastructure/IPictureHandler.cs b/Application/Contracts/Infrastructure/IPictureHandler.cs deleted file mode 100644 index 2662fc1..0000000 --- a/Application/Contracts/Infrastructure/IPictureHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Ardalis.Result; - -namespace Musify.Application.Contracts.Infrastructure -{ - public interface IPictureHandler - { - Task> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IPictureService.cs b/Application/Contracts/Infrastructure/IPictureService.cs new file mode 100644 index 0000000..66bc445 --- /dev/null +++ b/Application/Contracts/Infrastructure/IPictureService.cs @@ -0,0 +1,7 @@ +namespace Musify.Application.Contracts.Infrastructure +{ + public interface IPictureService + { + Task ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IStorageHandler.cs b/Application/Contracts/Infrastructure/IStorageHandler.cs deleted file mode 100644 index cb5d2df..0000000 --- a/Application/Contracts/Infrastructure/IStorageHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Ardalis.Result; - -namespace Musify.Application.Contracts.Infrastructure -{ - public interface IStorageHandler - { - Task> GetFileAsync(string bucket, string key, CancellationToken cancellationToken); - - Task GetUrlAsync(string bucket, string key, TimeSpan ExpirationTime, CancellationToken cancellationToken); - - Task> UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken); - - Task> UploadFileAsync(Stream sourceStream, string contentType, string bucket, string key, CancellationToken cancellationToken); - - Task TransferFilesAsync(string sourceDirectory, string bucket, string route, CancellationToken cancellationToken); - - Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken); - - Task CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey, - CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/Application/Contracts/Infrastructure/IStorageService.cs b/Application/Contracts/Infrastructure/IStorageService.cs new file mode 100644 index 0000000..beb5c85 --- /dev/null +++ b/Application/Contracts/Infrastructure/IStorageService.cs @@ -0,0 +1,20 @@ +namespace Musify.Application.Contracts.Infrastructure +{ + public interface IStorageService + { + Task GetFileAsync(string bucket, string key, CancellationToken cancellationToken); + + Task GetUrlAsync(string bucket, string key, TimeSpan ExpirationTime, CancellationToken cancellationToken); + + Task UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken); + + Task UploadFileAsync(Stream sourceStream, string contentType, string bucket, string key, CancellationToken cancellationToken); + + Task TransferFilesAsync(string sourceDirectory, string bucket, string route, CancellationToken cancellationToken); + + Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken); + + Task CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey, + CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs b/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs index 2ca01c9..0df4aa5 100644 --- a/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs +++ b/Application/PlayLists/Handlers/CreatePlayListCommandHandler.cs @@ -13,7 +13,7 @@ using Musify.Domain.Entities; namespace Musify.Application.PlayLists.Handlers { - public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler, + public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageService storageHandler, ILogger logger, ApplicationStorageConfiguration storageConfiguration, PlayListConfiguration playListConfiguration) : IRequestHandler> > { @@ -80,16 +80,16 @@ namespace Musify.Application.PlayLists.Handlers var pictureName = Guid.NewGuid() + picture.FileType; var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName); - var uploadResult = await storageHandler.UploadFileAsync(picture.FileStream, picture.ContentType, storageConfiguration.BucketName, pictureKey, cancellationToken); - if (!uploadResult.IsSuccess) + try { - logger.LogError("Failed to upload picture for playlist {PlayListId} to storage: {ErrorMessage}", - playList.Id, string.Join(", ", uploadResult.Errors)); - return uploadResult.ToErrorResult(); + await storageHandler.UploadFileAsync(picture.FileStream, picture.ContentType, storageConfiguration.BucketName, pictureKey, cancellationToken); + } + catch + { + return Result.Error($"An error occurred while uploading picture for playlist {playList.Id} to storage."); } playList.OriginalPictureName = pictureName; - return Result.Success(); } diff --git a/Application/PlayLists/Handlers/UpdatePlayListCommandHandler.cs b/Application/PlayLists/Handlers/UpdatePlayListCommandHandler.cs index 326f19b..965edaf 100644 --- a/Application/PlayLists/Handlers/UpdatePlayListCommandHandler.cs +++ b/Application/PlayLists/Handlers/UpdatePlayListCommandHandler.cs @@ -12,7 +12,7 @@ using Musify.Domain.Entities; namespace Musify.Application.PlayLists.Handlers { - public class UpdatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler, + public class UpdatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageService storageHandler, ILogger logger, ApplicationStorageConfiguration storageConfiguration, PlayListConfiguration playListConfiguration) : IRequestHandler> > @@ -80,19 +80,20 @@ namespace Musify.Application.PlayLists.Handlers var pictureName = Guid.NewGuid() + newPicture.FileType; var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName); - var uploadResult = await storageHandler.UploadFileAsync( - newPicture.FileStream, - newPicture.ContentType, - storageConfiguration.BucketName, - pictureKey, - cancellationToken); - - if (!uploadResult.IsSuccess) + try { - logger.LogError("Failed to upload picture for playlist {PlayListId} to storage: {ErrorMessage}", - playList.Id, string.Join("; ", uploadResult.Errors)); - return Result.Error($"Failed to upload playlist {playList.Id} picture"); + await storageHandler.UploadFileAsync( + newPicture.FileStream, + newPicture.ContentType, + storageConfiguration.BucketName, + pictureKey, + cancellationToken); } + catch + { + return Result.Error("Failed to upload picture to storage"); + } + playList.OriginalPictureName = pictureName; return Result.Success(pictureKey); } diff --git a/Application/Tracks/Handler/CreateTrackCommandHandler.cs b/Application/Tracks/Handler/CreateTrackCommandHandler.cs index 60fe524..3a16270 100644 --- a/Application/Tracks/Handler/CreateTrackCommandHandler.cs +++ b/Application/Tracks/Handler/CreateTrackCommandHandler.cs @@ -10,7 +10,7 @@ using Musify.Domain.Entities; namespace Musify.Application.Tracks.Handler { - public class CreateTrackCommandHandler(IDatabase database, IEventBus eventBus, IStorageHandler storageHandler, + public class CreateTrackCommandHandler(IDatabase database, IEventBus eventBus, IStorageService storageHandler, ILogger logger, ApplicationStorageConfiguration storageConfiguration, TrackConfiguration trackConfiguration) : IRequestHandler >> { @@ -49,33 +49,34 @@ namespace Musify.Application.Tracks.Handler string audioKey, CancellationToken cancellationToken) { - var pictureUpload = await storageHandler.UploadFileAsync( - request.Picture.FileStream, - request.Picture.ContentType, - storageConfiguration.BucketName, - pictureKey, - cancellationToken); - - if (!pictureUpload.IsSuccess) + try + { + await storageHandler.UploadFileAsync( + request.Picture.FileStream, + request.Picture.ContentType, + storageConfiguration.BucketName, + pictureKey, + cancellationToken); + } + catch { - var errors = string.Join(", ", pictureUpload.Errors); return Result.Error($"Failed to upload picture for {request.Title}"); } - var audioUpload = await storageHandler.UploadFileAsync( - request.Audio.FileStream, - request.Audio.ContentType, - storageConfiguration.BucketName, - audioKey, - cancellationToken); - - if (!audioUpload.IsSuccess) + try + { + await storageHandler.UploadFileAsync( + request.Audio.FileStream, + request.Audio.ContentType, + storageConfiguration.BucketName, + audioKey, + cancellationToken); + } + catch { await RollbackFilesAsync(pictureKey, audioKey, cancellationToken); - var errors = string.Join(", ", audioUpload.Errors); return Result.Error($"Failed to upload audio for {request.Title}"); } - return Result.Success(); } diff --git a/Application/Tracks/Handler/DeleteTrackCommandHandler.cs b/Application/Tracks/Handler/DeleteTrackCommandHandler.cs index a18cf24..fd57090 100644 --- a/Application/Tracks/Handler/DeleteTrackCommandHandler.cs +++ b/Application/Tracks/Handler/DeleteTrackCommandHandler.cs @@ -8,7 +8,7 @@ using Musify.Domain.Entities; namespace Musify.Application.Tracks.Handler { - public class DeleteTrackCommandHandler(IDatabase database, IStorageHandler storageHandler, ILogger logger, + public class DeleteTrackCommandHandler(IDatabase database, IStorageService storageHandler, ILogger logger, TrackConfiguration trackConfiguration, ApplicationStorageConfiguration storageConfiguration) : IRequestHandler> { diff --git a/Application/Users/Handlers/CreateUserCommandHandler.cs b/Application/Users/Handlers/CreateUserCommandHandler.cs index f085d77..fab1b64 100644 --- a/Application/Users/Handlers/CreateUserCommandHandler.cs +++ b/Application/Users/Handlers/CreateUserCommandHandler.cs @@ -20,10 +20,13 @@ namespace Musify.Application.Users.Handlers return Result.Conflict($"User {request.Id} already exists"); } - var keycloakUser = await keycloakUserClient.GetUserByIdAsync(request.Id.ToString(), cancellationToken); - if (!keycloakUser.IsSuccess) + KeycloakUserResponse keycloakUser; + try + { + keycloakUser = await keycloakUserClient.GetUserByIdAsync(request.Id.ToString(), cancellationToken); + } + catch { - logger.LogWarning("Keycloak user {UserId} not found", request.Id); return Result.NotFound("Keycloak user not found"); } @@ -34,9 +37,9 @@ namespace Musify.Application.Users.Handlers logger.LogInformation("Created user {UserId}", newUser.Id); return Result.Created(new UserResponse( newUser.Id.ToString(), - keycloakUser.Value.Name, - keycloakUser.Value.FirstName, - keycloakUser.Value.SecondName, + keycloakUser.Name, + keycloakUser.FirstName, + keycloakUser.SecondName, newUser.CreatedAt, newUser.UpdatedAt)); } diff --git a/Application/Users/Handlers/GetUserByIdQueryHandler.cs b/Application/Users/Handlers/GetUserByIdQueryHandler.cs index 1332472..47dd1ce 100644 --- a/Application/Users/Handlers/GetUserByIdQueryHandler.cs +++ b/Application/Users/Handlers/GetUserByIdQueryHandler.cs @@ -18,15 +18,21 @@ namespace Musify.Application.Users.Handlers if (user is null) return Result.NotFound("User not found"); - var keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken); - if (!keycloakUser.IsSuccess) - return Result.NotFound("Keycloak user not found"); + KeycloakUserResponse keycloakUser; + try + { + keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken); + } + catch + { + return Result.Error("Keycloak user not found"); + } return Result.Success(new UserResponse( user.Id.ToString(), - keycloakUser.Value.Name, - keycloakUser.Value.FirstName, - keycloakUser.Value.SecondName, + keycloakUser.Name, + keycloakUser.FirstName, + keycloakUser.SecondName, user.CreatedAt, user.UpdatedAt)); } diff --git a/Application/Users/Handlers/GetUserByKeycloakQueryHandler.cs b/Application/Users/Handlers/GetUserByKeycloakQueryHandler.cs index d46792d..b8e59f0 100644 --- a/Application/Users/Handlers/GetUserByKeycloakQueryHandler.cs +++ b/Application/Users/Handlers/GetUserByKeycloakQueryHandler.cs @@ -18,15 +18,21 @@ namespace Musify.Application.Users.Handlers if (user is null) return Result.NotFound("User not found"); - var keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken); - if (!keycloakUser.IsSuccess) + KeycloakUserResponse keycloakUser; + try + { + keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken); + } + catch + { return Result.NotFound("Keycloak user not found"); + } return Result.Success(new UserResponse( user.Id.ToString(), - keycloakUser.Value.Name, - keycloakUser.Value.FirstName, - keycloakUser.Value.SecondName, + keycloakUser.Name, + keycloakUser.FirstName, + keycloakUser.SecondName, user.CreatedAt, user.UpdatedAt)); } diff --git a/Infrastructure/Audio/AudioTranscoderDependencyInjection.cs b/Infrastructure/Audio/AudioTranscoderDependencyInjection.cs deleted file mode 100644 index f7e04c2..0000000 --- a/Infrastructure/Audio/AudioTranscoderDependencyInjection.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Musify.Application.Contracts.Infrastructure; - -namespace Musify.Infrastructure.Audio -{ - public static class AudioTranscoderDependencyInjection - { - public static IServiceCollection AddAudioTranscoder(this IServiceCollection serviceDescriptors) - { - serviceDescriptors.AddScoped(); - return serviceDescriptors; - } - } -} \ No newline at end of file diff --git a/Infrastructure/Configuration/ConfigurationDependencyInjection.cs b/Infrastructure/Configuration/ConfigurationDependencyInjection.cs deleted file mode 100644 index b89a9eb..0000000 --- a/Infrastructure/Configuration/ConfigurationDependencyInjection.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Musify.Infrastructure.Configuration -{ - public static class ConfigurationDependencyInjection - { - public static IServiceCollection AddInfrastructureConfigurations(this IServiceCollection serviceDescriptors, IConfiguration configuration) - { - serviceDescriptors - .AddValidatedConfiguration(configuration, DatabaseConfiguration.SectionName) - .AddValidatedConfiguration(configuration, KeycloakConfiguration.SectionName) - .AddValidatedConfiguration(configuration, InfrastructureStorageConfiguration.SectionName) - .AddValidatedConfiguration(configuration, MessagingConfiguration.SectionName) - .AddValidatedConfiguration(configuration, OpenTelemetryConfiguration.SectionName) - .AddValidatedConfiguration(configuration, AudioTranscoderConfiguration.SectionName) - .AddValidatedConfiguration(configuration, WorkerConfiguration.SectionName); - - return serviceDescriptors; - } - - static IServiceCollection AddValidatedConfiguration( - this IServiceCollection serviceDescriptors, - IConfiguration configuration, - string sectionName) - where TConfiguration : class - { - serviceDescriptors - .AddOptionsWithValidateOnStart() - .Bind(configuration.GetRequiredSection(sectionName)) - .ValidateDataAnnotations(); - - serviceDescriptors.AddSingleton(serviceProvider => - serviceProvider.GetRequiredService>().Value); - - return serviceDescriptors; - } - } -} diff --git a/Infrastructure/Configuration/MessagingConfiguration.cs b/Infrastructure/Configuration/MassTransitConfiguration.cs similarity index 90% rename from Infrastructure/Configuration/MessagingConfiguration.cs rename to Infrastructure/Configuration/MassTransitConfiguration.cs index 045ea0c..e92ccd6 100644 --- a/Infrastructure/Configuration/MessagingConfiguration.cs +++ b/Infrastructure/Configuration/MassTransitConfiguration.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; namespace Musify.Infrastructure.Configuration { - public class MessagingConfiguration + public class MassTransitConfiguration { public const string SectionName = "Messaging"; diff --git a/Infrastructure/Identity/KeycloakDependencyInjection.cs b/Infrastructure/Identity/KeycloakDependencyInjection.cs deleted file mode 100644 index ff03f43..0000000 --- a/Infrastructure/Identity/KeycloakDependencyInjection.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Keycloak.Net; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Musify.Application.Contracts.Infrastructure; -using Musify.Infrastructure.Configuration; - -namespace Musify.Infrastructure.Identity -{ - public static class KeycloakDependencyInjection - { - public static IServiceCollection AddKeycloakService(this IServiceCollection serviceDescriptors, IConfiguration configuration) - { - serviceDescriptors.AddOptionsWithValidateOnStart() - .Bind(configuration.GetRequiredSection(KeycloakConfiguration.SectionName)) - .ValidateDataAnnotations(); - - serviceDescriptors.AddScoped(serviceProvider => - { - var keycloakConfiguration = serviceProvider.GetRequiredService(); - - return new KeycloakClient( - keycloakConfiguration.BaseUrl, - keycloakConfiguration.Username, - keycloakConfiguration.Password); - } ); - serviceDescriptors.AddScoped(); - return serviceDescriptors; - } - } -} \ No newline at end of file diff --git a/Infrastructure/Identity/KeycloakUserMapper.cs b/Infrastructure/Identity/KeycloakUserMapper.cs deleted file mode 100644 index fdac511..0000000 --- a/Infrastructure/Identity/KeycloakUserMapper.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Keycloak.Net.Models.Users; -using Musify.Application.Users.Responses; - -namespace Musify.Infrastructure.Identity -{ - public static class KeycloakUserMapper - { - public static KeycloakUserResponse Map(User keycloakUser) - { - return new KeycloakUserResponse( - Guid.Parse(keycloakUser.Id ?? throw new InvalidOperationException("Keycloak user Id is required.")), - keycloakUser.UserName ?? throw new InvalidOperationException("Keycloak user Name is required."), - keycloakUser.FirstName ?? throw new InvalidOperationException("Keycloak user FirstName is required."), - keycloakUser.LastName ?? throw new InvalidOperationException("Keycloak user SecondName is required.")); - } - } -} \ No newline at end of file diff --git a/Infrastructure/InfrastructureDependencyInjection.cs b/Infrastructure/InfrastructureDependencyInjection.cs deleted file mode 100644 index d460c83..0000000 --- a/Infrastructure/InfrastructureDependencyInjection.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Musify.Application.Contracts.Infrastructure; -using Musify.Infrastructure.Audio; -using Musify.Infrastructure.Configuration; -using Musify.Infrastructure.Identity; -using Musify.Infrastructure.MassTransit; -using Musify.Infrastructure.Observability; -using Musify.Infrastructure.Persistence; -using Musify.Infrastructure.Pictures; -using Musify.Infrastructure.Storage; - -namespace Musify.Infrastructure -{ - public static class InfrastructureDependencyInjection - { - public static IServiceCollection AddInfrastructureModules(this IServiceCollection serviceDescriptors, IConfiguration configuration) - { - serviceDescriptors - .AddInfrastructureConfigurations(configuration) - .AddDatabase(configuration) - .AddKeycloakService(configuration) - .AddStorageHandler() - .AddPictureHandler() - .AddAudioTranscoder() - .AddInfrastructureOpenTelemetry() - .AddMassTransitClient() - .AddScoped() - .AddScoped(); - - return serviceDescriptors; - } - - public static IServiceCollection AddInfrastructureWorkerModules(this IServiceCollection serviceDescriptors, IConfiguration configuration) - { - serviceDescriptors - .AddInfrastructureConfigurations(configuration) - .AddDatabase(configuration) - .AddStorageHandler() - .AddPictureHandler() - .AddAudioTranscoder() - .AddInfrastructureOpenTelemetry() - .AddMassTransitConsumers() - .AddScoped() - .AddScoped(); - - return serviceDescriptors; - } - } -} diff --git a/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs b/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs index 063e641..c14fe92 100644 --- a/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs +++ b/Infrastructure/MassTransit/Activities/Audio/TranscodeDashAudioActivity.cs @@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs; namespace Musify.Infrastructure.MassTransit.Activities { public class TranscodeDashAudioActivity( - IAudioTranscoder audioTranscoder, + IAudioTranscoderService audioTranscoder, ILogger logger, IProcessTrackingStore processTrackingStore) : IActivity @@ -40,18 +40,10 @@ namespace Musify.Infrastructure.MassTransit.Activities { await using (var fileStream = File.OpenRead(sourceFilePath)) { - var result = await audioTranscoder.TranscodeToDashAsync( + await audioTranscoder.TranscodeToDashAsync( fileStream, workingDirectory, executeContext.CancellationToken); - - if (!result.IsSuccess) - { - var errorMessage = string.Join("; ", result.Errors); - logger.LogError("Transcoding failed for {SourceFilePath}: {Errors}", - sourceFilePath, errorMessage); - throw new Exception(errorMessage); - } } File.Delete(sourceFilePath); diff --git a/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs index 2a25174..df6bd52 100644 --- a/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs +++ b/Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs @@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs; namespace Musify.Infrastructure.MassTransit.Activities { public class DownloadFileFromBucketActivity( - IStorageHandler storageHandler, + IStorageService storageHandler, ILogger logger, IProcessTrackingStore processTrackingStore) : IActivity @@ -36,26 +36,15 @@ namespace Musify.Infrastructure.MassTransit.Activities try { - var getFile = await storageHandler.GetFileAsync( + using var fileStream = await storageHandler.GetFileAsync( executeContext.Arguments.Bucket, executeContext.Arguments.Key, executeContext.CancellationToken); - if (!getFile.IsSuccess) - { - var errorMessage = string.Join("; ", getFile.Errors); - logger.LogWarning("Failed to download {Bucket}/{Key}: {Errors}", - executeContext.Arguments.Bucket, - executeContext.Arguments.Key, - errorMessage); - throw new Exception(errorMessage); - } - var destinationDirectory = Path.GetDirectoryName(destinationPath); if (!string.IsNullOrWhiteSpace(destinationDirectory)) Directory.CreateDirectory(destinationDirectory); - using var fileStream = getFile.Value; if (fileStream.CanSeek) fileStream.Position = 0; diff --git a/Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs index 0bf6530..e1ddbf8 100644 --- a/Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs +++ b/Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs @@ -7,7 +7,7 @@ using Musify.Infrastructure.MassTransit.Activities.Arguments; namespace Musify.Infrastructure.MassTransit.Activities { public class RemoveFileFromBucketActivity( - IStorageHandler storageHandler, + IStorageService storageHandler, ILogger logger, IProcessTrackingStore processTrackingStore) : IExecuteActivity @@ -32,20 +32,11 @@ namespace Musify.Infrastructure.MassTransit.Activities try { - var removeFile = await storageHandler.RemoveFileAsync( + await storageHandler.RemoveFileAsync( executeContext.Arguments.Bucket, executeContext.Arguments.Key, executeContext.CancellationToken); - if (!removeFile.IsSuccess) - { - var errorMessage = string.Join("; ", removeFile.Errors); - logger.LogWarning("Failed to remove {Key} from bucket {Bucket}: {Errors}", - executeContext.Arguments.Key, - executeContext.Arguments.Bucket, - errorMessage); - } - await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken); return executeContext.Completed(); } diff --git a/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs index 81b2101..f0bc572 100644 --- a/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs +++ b/Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs @@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs; namespace Musify.Infrastructure.MassTransit.Activities { public class TransferFilesToBucketActivity( - IStorageHandler storageHandler, + IStorageService storageHandler, ILogger logger, IProcessTrackingStore processTrackingStore) : IActivity @@ -42,23 +42,12 @@ namespace Musify.Infrastructure.MassTransit.Activities .Select(file => Path.Combine(destinationKey, Path.GetFileName(file))) .ToArray(); - var transferFilesResult = await storageHandler.TransferFilesAsync( + await storageHandler.TransferFilesAsync( folderPath, executeContext.Arguments.DestinationBucket, destinationKey, executeContext.CancellationToken); - if (!transferFilesResult.IsSuccess) - { - var errorMessage = string.Join("; ", transferFilesResult.Errors); - logger.LogWarning("Failed to transfer files from {FolderPath} to {DestinationBucket}/{DestinationKey}: {Errors}", - folderPath, - executeContext.Arguments.DestinationBucket, - destinationKey, - errorMessage); - throw new Exception(errorMessage); - } - await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken); return executeContext.Completed(); } diff --git a/Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs b/Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs index 32a51e6..99849c1 100644 --- a/Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs +++ b/Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs @@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Logs; namespace Musify.Infrastructure.MassTransit.Activities.Files { - public class UploadFileToBucketActivity(IStorageHandler storageHandler, + public class UploadFileToBucketActivity(IStorageService storageHandler, ILogger logger, IProcessTrackingStore processTrackingStore) : IActivity @@ -37,22 +37,12 @@ namespace Musify.Infrastructure.MassTransit.Activities.Files try { - var uploadResult = await storageHandler.UploadFileAsync( + await storageHandler.UploadFileAsync( sourceFilePath, executeContext.Arguments.DestinationBucket, Path.Combine(executeContext.Arguments.DestinationRoute, fileName), executeContext.CancellationToken); - if (!uploadResult.IsSuccess) - { - var errors = string.Join("; ", uploadResult.Errors); - logger.LogWarning("Failed to upload file to bucket {Bucket}/{Route}: {Errors}", - executeContext.Arguments.DestinationBucket, - executeContext.Arguments.DestinationRoute, - errors); - throw new Exception(errors); - } - await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken); return executeContext.CompletedWithVariables(new UploadFileToBucketLog( executeContext.Arguments.DestinationBucket, @@ -70,19 +60,10 @@ namespace Musify.Infrastructure.MassTransit.Activities.Files { try { - var uploadResult = await storageHandler.RemoveFileAsync( + await storageHandler.RemoveFileAsync( compensateContext.Log.DestinationBucket, compensateContext.Log.DestinationKey, compensateContext.CancellationToken); - - if (!uploadResult.IsSuccess) - { - var errors = string.Join("; ", uploadResult.Errors); - logger.LogError("Failed to remove file from bucket during compensation. Bucket: {Bucket}, Key: {Key}, Errors: {Errors}", - compensateContext.Log.DestinationBucket, compensateContext.Log.DestinationKey, errors); - - return compensateContext.Failed(new Exception($"Failed to remove file from bucket during compensation. Bucket: {compensateContext.Log.DestinationBucket}, Key: {compensateContext.Log.DestinationKey}, Errors: {errors}")); - } return compensateContext.Compensated(); } catch (Exception exception) diff --git a/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs b/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs index 710d916..c264d41 100644 --- a/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs +++ b/Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs @@ -9,7 +9,7 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; namespace Musify.Infrastructure.MassTransit.Activities { public class ResizePictureActivity( - IPictureHandler pictureHandler, + IPictureService pictureHandler, ILogger logger, IProcessTrackingStore processTrackingStore) : IActivity @@ -53,23 +53,12 @@ namespace Musify.Infrastructure.MassTransit.Activities executeContext.Arguments.Height, executeContext.CancellationToken); - if (!resizedPicture.IsSuccess) - { - var errorMessage = string.Join("; ", resizedPicture.Errors); - logger.LogError("Failed to resize picture {SourceFilePath} to {Width}x{Height}: {Errors}", - sourceFilePath, - executeContext.Arguments.Width, - executeContext.Arguments.Height, - errorMessage); - throw new Exception(errorMessage); - } - var destinationDirectory = Path.GetDirectoryName(destinationFilePath); if (!string.IsNullOrEmpty(destinationDirectory)) Directory.CreateDirectory(destinationDirectory); await using var destinationStream = File.Create(destinationFilePath); - await resizedPicture.Value.CopyToAsync(destinationStream, executeContext.CancellationToken); + await resizedPicture.CopyToAsync(destinationStream, executeContext.CancellationToken); logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}", sourceFilePath, diff --git a/Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs b/Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs index 2da7293..d55724b 100644 --- a/Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs +++ b/Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs @@ -23,7 +23,7 @@ namespace Musify.Infrastructure.MassTransit options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) => ConfigureRabbitMqHost( busFactoryConfigurator, - busRegistrationContext.GetRequiredService())); + busRegistrationContext.GetRequiredService())); } ); return serviceDescriptors; @@ -60,7 +60,7 @@ namespace Musify.Infrastructure.MassTransit { ConfigureRabbitMqHost( busFactoryConfigurator, - busRegistrationContext.GetRequiredService()); + busRegistrationContext.GetRequiredService()); busFactoryConfigurator.UseConsumeFilter(typeof(ProcessTrackingConsumeFilter<>), busRegistrationContext); @@ -141,7 +141,7 @@ namespace Musify.Infrastructure.MassTransit static void ConfigureRabbitMqHost( IRabbitMqBusFactoryConfigurator busFactoryConfigurator, - MessagingConfiguration massTransitConfiguration) + MassTransitConfiguration massTransitConfiguration) { busFactoryConfigurator.Host(massTransitConfiguration.Host, options => { diff --git a/Infrastructure/Persistence/DatabaseDependencyInjection.cs b/Infrastructure/Persistence/DatabaseDependencyInjection.cs index 4152976..0a3f01c 100644 --- a/Infrastructure/Persistence/DatabaseDependencyInjection.cs +++ b/Infrastructure/Persistence/DatabaseDependencyInjection.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Musify.Application.Contracts.Infrastructure; +using Musify.Infrastructure.Configuration; namespace Musify.Infrastructure.Persistence { @@ -8,6 +10,14 @@ namespace Musify.Infrastructure.Persistence { public static IServiceCollection AddDatabase(this IServiceCollection serviceDescriptors, IConfiguration configuration) { + serviceDescriptors + .AddOptionsWithValidateOnStart() + .Bind(configuration.GetRequiredSection(DatabaseConfiguration.SectionName)) + .ValidateDataAnnotations(); + + serviceDescriptors.AddSingleton(serviceProvider => + serviceProvider.GetRequiredService>().Value); + serviceDescriptors.AddDbContext(); serviceDescriptors.AddScoped(serviceProvider => serviceProvider.GetRequiredService()); diff --git a/Infrastructure/Pictures/PictureDependencyInjection.cs b/Infrastructure/Pictures/PictureDependencyInjection.cs deleted file mode 100644 index 4d955c7..0000000 --- a/Infrastructure/Pictures/PictureDependencyInjection.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Musify.Application.Contracts.Infrastructure; - -namespace Musify.Infrastructure.Pictures -{ - public static class PictureDependencyInjection - { - public static IServiceCollection AddPictureHandler(this IServiceCollection serviceDescriptors) - { - serviceDescriptors.AddScoped(); - return serviceDescriptors; - } - } -} \ No newline at end of file diff --git a/Infrastructure/Audio/AudioTranscoder.cs b/Infrastructure/Services/AudioTranscoderService.cs similarity index 88% rename from Infrastructure/Audio/AudioTranscoder.cs rename to Infrastructure/Services/AudioTranscoderService.cs index bb7cd06..48c38d8 100644 --- a/Infrastructure/Audio/AudioTranscoder.cs +++ b/Infrastructure/Services/AudioTranscoderService.cs @@ -1,17 +1,18 @@ -using Ardalis.Result; using Microsoft.Extensions.Logging; using Musify.Application.Contracts.Infrastructure; using Musify.Infrastructure.Configuration; using System.Diagnostics; -namespace Musify.Infrastructure.Audio +namespace Musify.Infrastructure.Services { - public class AudioTranscoder( - ILogger logger, + public class AudioTranscoderService( + ILogger logger, AudioTranscoderConfiguration audioTranscoderConfiguration) - : IAudioTranscoder + : IAudioTranscoderService { - public async Task TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken) + record FfmpegExecutionResult(int ExitCode, string StandardOutput, string StandardError); + + public async Task TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken) { try { @@ -30,30 +31,32 @@ namespace Musify.Infrastructure.Audio { logger.LogError("Audio transcoding to DASH failed with exit code {ExitCode}. Output: {StandardOutput}, Error: {StandardError}", executionResult.ExitCode, executionResult.StandardOutput, executionResult.StandardError); - return Result.Error($"Audio transcoding failed (ExitCode: {executionResult.ExitCode})"); + } + else + { + logger.LogInformation("Audio transcoding to DASH completed for {DestinationPath}", destinationPath); } - logger.LogInformation("Audio transcoding to DASH completed for {DestinationPath}", destinationPath); - return Result.Success(); + return executionResult.ExitCode; } catch (TimeoutException timeoutException) { logger.LogError(timeoutException, "Audio transcoding timed out after {Timeout} for {DestinationPath}", audioTranscoderConfiguration.TranscodingTimeout, destinationPath); - return Result.Error("Audio transcoding timed out"); + throw; } catch (Exception exception) { logger.LogError(exception, "Failed to transcode audio to DASH for {DestinationPath}", destinationPath); - return Result.Error(exception.Message); + throw; } } - public async Task> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken) + public async Task IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken) { if (!File.Exists(filePath)) { logger.LogWarning("Audio validation requested for non-existing file {FilePath}", filePath); - return Result.Error($"Audio file not found: {filePath}"); + return false; } try @@ -76,18 +79,17 @@ namespace Musify.Infrastructure.Audio logger.LogDebug("Audio validation successful for {FilePath}", filePath); } - return Result.Success(isValid); + return isValid; } catch (TimeoutException timeoutException) { logger.LogError(timeoutException, "Audio validation timed out for {FilePath}", filePath); - return Result.Error("Audio validation timed out."); } catch (Exception exception) { logger.LogError(exception, "Failed to validate audio file {FilePath}", filePath); - return Result.Error(exception.Message); } + return false; } async Task ExecuteFfmpegWithInputAsync( @@ -216,7 +218,5 @@ namespace Musify.Infrastructure.Audio static string BuildValidateAudioArguments(string filePath) => $"-v error -i \"{filePath}\" -map 0:a:0 -f null -"; - - sealed record FfmpegExecutionResult(int ExitCode, string StandardOutput, string StandardError); } } \ No newline at end of file diff --git a/Infrastructure/Identity/KeycloakUserService.cs b/Infrastructure/Services/KeycloakUserService.cs similarity index 68% rename from Infrastructure/Identity/KeycloakUserService.cs rename to Infrastructure/Services/KeycloakUserService.cs index 7efa649..681f870 100644 --- a/Infrastructure/Identity/KeycloakUserService.cs +++ b/Infrastructure/Services/KeycloakUserService.cs @@ -1,4 +1,3 @@ -using Ardalis.Result; using Keycloak.Net; using Keycloak.Net.Models.Users; using Microsoft.Extensions.Logging; @@ -6,14 +5,14 @@ using Musify.Application.Contracts.Infrastructure; using Musify.Application.Users.Responses; using Musify.Infrastructure.Configuration; -namespace Musify.Infrastructure.Identity +namespace Musify.Infrastructure.Services { public class KeycloakUserService( KeycloakClient keycloakClient, KeycloakConfiguration keycloakConfiguration, ILogger logger) : IKeycloakUserService { - public async Task> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken) + public async Task GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken) { User keycloakUser; @@ -24,14 +23,14 @@ namespace Musify.Infrastructure.Identity catch (OperationCanceledException) { logger.LogWarning("Operation to retrieve Keycloak user {KeycloakId} was cancelled", keycloakId); - return Result.Error("Operation cancelled"); + throw; } catch (Exception exception) { logger.LogWarning(exception, "Failed to retrieve Keycloak user {KeycloakId}", keycloakId); - return Result.NotFound(); + throw; } - return Result.Success(KeycloakUserMapper.Map(keycloakUser)); + return ToResponse(keycloakUser); } public async Task> GetUsersAsync(CancellationToken cancellationToken, string search = "", int first = 0, int max = 20, string username = "") @@ -45,7 +44,7 @@ namespace Musify.Infrastructure.Identity cancellationToken: cancellationToken); return users - .Select(KeycloakUserMapper.Map) + .Select(ToResponse) .ToArray(); } @@ -57,5 +56,14 @@ namespace Musify.Infrastructure.Identity username: username, cancellationToken: cancellationToken); } + + KeycloakUserResponse ToResponse(User keycloakUser) + { + return new KeycloakUserResponse( + Guid.Parse(keycloakUser.Id ?? throw new InvalidOperationException("Keycloak user Id is required.")), + keycloakUser.UserName ?? throw new InvalidOperationException("Keycloak user Name is required."), + keycloakUser.FirstName ?? throw new InvalidOperationException("Keycloak user FirstName is required."), + keycloakUser.LastName ?? throw new InvalidOperationException("Keycloak user SecondName is required.")); + } } } \ No newline at end of file diff --git a/Infrastructure/Pictures/PictureHandler.cs b/Infrastructure/Services/PictureService.cs similarity index 74% rename from Infrastructure/Pictures/PictureHandler.cs rename to Infrastructure/Services/PictureService.cs index b1ab220..f50f9ba 100644 --- a/Infrastructure/Pictures/PictureHandler.cs +++ b/Infrastructure/Services/PictureService.cs @@ -1,15 +1,14 @@ -using Ardalis.Result; using Microsoft.Extensions.Logging; using Musify.Application.Contracts.Infrastructure; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; -namespace Musify.Infrastructure.Pictures +namespace Musify.Infrastructure.Services { - public class PictureHandler(ILogger logger) - : IPictureHandler + public class PictureService(ILogger logger) + : IPictureService { - public async Task> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken) + public async Task ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken) { try { @@ -18,7 +17,6 @@ namespace Musify.Infrastructure.Pictures pictureStream.Position = 0; using var picture = await Image.LoadAsync(pictureStream, cancellationToken); - picture.Mutate(options => options.Resize(new ResizeOptions { Size = new Size(width, height), @@ -26,18 +24,16 @@ namespace Musify.Infrastructure.Pictures } )); var memoryStream = new MemoryStream(); - await picture.SaveAsWebpAsync(memoryStream, cancellationToken); - memoryStream.Position = 0; logger.LogDebug("Picture resized to {Width}x{Height}, length: {Length}", width, height, memoryStream.Length); - return Result.Success(memoryStream); + return memoryStream; } catch (Exception exception) { logger.LogError(exception, "Failed to resize picture to {Width}x{Height}", width, height); - return Result.Error("Failed to resize picture"); + throw; } } } diff --git a/Infrastructure/Services/ServicesDependencyInjection.cs b/Infrastructure/Services/ServicesDependencyInjection.cs new file mode 100644 index 0000000..e9cba44 --- /dev/null +++ b/Infrastructure/Services/ServicesDependencyInjection.cs @@ -0,0 +1,81 @@ +using Amazon.S3; +using Keycloak.Net; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Musify.Application.Contracts.Infrastructure; +using Musify.Infrastructure.Configuration; + +namespace Musify.Infrastructure.Services +{ + public static class ServicesDependencyInjection + { + public static IServiceCollection AddStorageService(this IServiceCollection serviceDescriptors, IConfiguration configuration) + { + serviceDescriptors + .AddOptionsWithValidateOnStart() + .Bind(configuration.GetRequiredSection(InfrastructureStorageConfiguration.SectionName)) + .ValidateDataAnnotations(); + + serviceDescriptors.AddSingleton(serviceProvider => + serviceProvider.GetRequiredService>().Value); + + serviceDescriptors.AddScoped(serviceProvider => + { + var configuration = serviceProvider.GetRequiredService(); + var s3Configuration = new AmazonS3Config + { + ServiceURL = configuration.ServiceUrl, + ForcePathStyle = configuration.ForcePathStyle, + UseHttp = configuration.UseHttp, + }; + return new AmazonS3Client(configuration.AccessKey, configuration.SecretAccessKey, s3Configuration); + } ); + + serviceDescriptors.AddScoped(); + return serviceDescriptors; + } + + public static IServiceCollection AddPictureService(this IServiceCollection serviceDescriptors, IConfiguration configuration) + { + serviceDescriptors.AddScoped(); + return serviceDescriptors; + } + + public static IServiceCollection AddKeycloakService(this IServiceCollection serviceDescriptors, IConfiguration configuration) + { + serviceDescriptors.AddOptionsWithValidateOnStart() + .Bind(configuration.GetRequiredSection(KeycloakConfiguration.SectionName)) + .ValidateDataAnnotations(); + + serviceDescriptors.AddSingleton(serviceProvider => + serviceProvider.GetRequiredService>().Value); + + serviceDescriptors.AddScoped(serviceProvider => + { + var keycloakConfiguration = serviceProvider.GetRequiredService(); + + return new KeycloakClient( + keycloakConfiguration.BaseUrl, + keycloakConfiguration.Username, + keycloakConfiguration.Password); + } ); + serviceDescriptors.AddScoped(); + return serviceDescriptors; + } + + public static IServiceCollection AddAudioTranscoder(this IServiceCollection serviceDescriptors, IConfiguration configuration) + { + serviceDescriptors + .AddOptionsWithValidateOnStart() + .Bind(configuration.GetRequiredSection(AudioTranscoderConfiguration.SectionName)) + .ValidateDataAnnotations(); + + serviceDescriptors.AddSingleton(serviceProvider => + serviceProvider.GetRequiredService>().Value); + + serviceDescriptors.AddScoped(); + return serviceDescriptors; + } + } +} \ No newline at end of file diff --git a/Infrastructure/Storage/StorageHandler.cs b/Infrastructure/Services/StorageService.cs similarity index 74% rename from Infrastructure/Storage/StorageHandler.cs rename to Infrastructure/Services/StorageService.cs index 5b7c6f1..8f19a6d 100644 --- a/Infrastructure/Storage/StorageHandler.cs +++ b/Infrastructure/Services/StorageService.cs @@ -1,19 +1,18 @@ using Amazon.S3; using Amazon.S3.Model; using Amazon.S3.Transfer; -using Ardalis.Result; using Microsoft.Extensions.Logging; using MimeMapping; using Musify.Application.Contracts.Infrastructure; using Musify.Domain.Entities; using Musify.Infrastructure.Configuration; -namespace Musify.Infrastructure.Storage +namespace Musify.Infrastructure.Services { - public class StorageHandler(IDatabase database, IAmazonS3 amazonS3, InfrastructureStorageConfiguration storageClientConfiguration, ILogger logger) - : IStorageHandler + public class StorageService(IDatabase database, IAmazonS3 amazonS3, InfrastructureStorageConfiguration storageClientConfiguration, ILogger logger) + : IStorageService { - public async Task> GetFileAsync(string bucket, string key, CancellationToken cancellationToken) + public async Task GetFileAsync(string bucket, string key, CancellationToken cancellationToken) { var request = new GetObjectRequest { @@ -24,14 +23,13 @@ namespace Musify.Infrastructure.Storage try { var response = await amazonS3.GetObjectAsync(request, cancellationToken); - logger.LogDebug("Retrieved file from S3 {Bucket}/{Key}", bucket, key); - return Result.Success(response.ResponseStream); + return response.ResponseStream; } catch (Exception exception) { - logger.LogWarning(exception, "Failed to get file from S3 {Bucket}/{Key}", bucket, key); - return Result.NotFound($"File {bucket}/{key} not found"); + logger.LogError(exception, "Failed to get file from S3 {Bucket}/{Key}", bucket, key); + throw; } } @@ -46,12 +44,10 @@ namespace Musify.Infrastructure.Storage }; logger.LogDebug("Generating pre-signed URL for {Bucket}/{Key}", bucket, key); - var url = await amazonS3.GetPreSignedURLAsync(request); - - return url; + return await amazonS3.GetPreSignedURLAsync(request); } - public async Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken) + public async Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken) { var request = new DeleteObjectRequest { @@ -60,12 +56,10 @@ namespace Musify.Infrastructure.Storage }; logger.LogDebug("Removing file from S3 {Bucket}/{Key}", bucket, key); - var response = await amazonS3.DeleteObjectAsync(request, cancellationToken); - - return Result.NoContent(); + await amazonS3.DeleteObjectAsync(request, cancellationToken); } - public async Task TransferFilesAsync(string sourceDirectory, string bucket, string route, CancellationToken cancellationToken) + public async Task TransferFilesAsync(string sourceDirectory, string bucket, string route, CancellationToken cancellationToken) { var trasnsferUtility = new TransferUtility(amazonS3); @@ -101,31 +95,30 @@ namespace Musify.Infrastructure.Storage failedCount++; upload.State = UploadState.Failed; logger.LogError(exception, "Failed to transfer file to S3 {Bucket}/{Key}", bucket, key); + throw; } } await database.SaveChangesAsync(cancellationToken); logger.LogInformation("File transfer completed to {Bucket}/{Route}. Success: {SuccessCount}, Failed: {FailedCount}", bucket, route, successCount, failedCount); - - return Result.Success(); } - public async Task> UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken) + public async Task UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken) { try { using var fileStream = File.OpenRead(filePath); var contentType = MimeUtility.GetMimeMapping(filePath); - return await UploadFileAsync(fileStream, contentType, bucket, key, cancellationToken); + await UploadFileAsync(fileStream, contentType, bucket, key, cancellationToken); } catch (Exception exception) { logger.LogError(exception, "Failed to upload file to S3 {Bucket}/{Key}", bucket, key); - return Result.Error($"Failed to upload to {bucket}/{key}"); + throw; } } - public async Task> UploadFileAsync(Stream sourceStream, string contentType, string bucket, string key, CancellationToken cancellationToken) + public async Task UploadFileAsync(Stream sourceStream, string contentType, string bucket, string key, CancellationToken cancellationToken) { var request = new PutObjectRequest { @@ -153,17 +146,16 @@ namespace Musify.Infrastructure.Storage { logger.LogError(exception, "Failed to upload file to S3 {Bucket}/{Key}", bucket, key); upload.State = UploadState.Failed; - return Result.Error($"Failed to upload to {bucket}/{key}"); + await database.SaveChangesAsync(cancellationToken); + throw; } upload.State = UploadState.Successful; await database.SaveChangesAsync(cancellationToken); logger.LogDebug("Saved upload record for {Bucket}/{Key}", bucket, key); - - return Result.Success(key); } - public async Task CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey, + public async Task CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey, CancellationToken cancellationToken) { var request = new CopyObjectRequest @@ -179,13 +171,12 @@ namespace Musify.Infrastructure.Storage var response = await amazonS3.CopyObjectAsync(request, cancellationToken); logger.LogInformation("Copied file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}", sourceBucket, sourceKey, destinationBucket, destinationKey); - return Result.Success(); } catch (Exception exception) { logger.LogError(exception, "Failed to copy file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}", sourceBucket, sourceKey, destinationBucket, destinationKey); - return Result.Error($"Failed to copy from {sourceBucket}/{sourceKey} to {destinationBucket}/{destinationKey}"); + throw; } } } diff --git a/Infrastructure/Storage/StorageDependencyInjection.cs b/Infrastructure/Storage/StorageDependencyInjection.cs deleted file mode 100644 index 5e2f4ae..0000000 --- a/Infrastructure/Storage/StorageDependencyInjection.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Amazon.S3; -using Microsoft.Extensions.DependencyInjection; -using Musify.Application.Contracts.Infrastructure; -using Musify.Infrastructure.Configuration; - -namespace Musify.Infrastructure.Storage -{ - public static class StorageDependencyInjection - { - public static IServiceCollection AddStorageHandler(this IServiceCollection serviceDescriptors) - { - serviceDescriptors.AddScoped(serviceProvider => - { - var configuration = serviceProvider.GetRequiredService(); - var s3Configuration = new AmazonS3Config - { - ServiceURL = configuration.ServiceUrl, - ForcePathStyle = configuration.ForcePathStyle, - UseHttp = configuration.UseHttp, - }; - return new AmazonS3Client(configuration.AccessKey, configuration.SecretAccessKey, s3Configuration); - } ); - - serviceDescriptors.AddScoped(); - return serviceDescriptors; - } - } -} \ No newline at end of file