Experimental changes
This commit is contained in:
@@ -1,11 +0,0 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
|
|
||||||
namespace Musify.Application.Contracts.Infrastructure
|
|
||||||
{
|
|
||||||
public interface IAudioTranscoder
|
|
||||||
{
|
|
||||||
Task<Result> TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result<bool>> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Musify.Application.Contracts.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IAudioTranscoderService
|
||||||
|
{
|
||||||
|
Task<int> TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<bool> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
using Ardalis.Result;
|
using Musify.Application.Users.Responses;
|
||||||
using Musify.Application.Users.Responses;
|
|
||||||
|
|
||||||
namespace Musify.Application.Contracts.Infrastructure
|
namespace Musify.Application.Contracts.Infrastructure
|
||||||
{
|
{
|
||||||
public interface IKeycloakUserService
|
public interface IKeycloakUserService
|
||||||
{
|
{
|
||||||
Task<Result<KeycloakUserResponse>> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken);
|
Task<KeycloakUserResponse> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<IReadOnlyCollection<KeycloakUserResponse>> GetUsersAsync(
|
Task<IReadOnlyCollection<KeycloakUserResponse>> GetUsersAsync(
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
|
|
||||||
namespace Musify.Application.Contracts.Infrastructure
|
|
||||||
{
|
|
||||||
public interface IPictureHandler
|
|
||||||
{
|
|
||||||
Task<Result<Stream>> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Musify.Application.Contracts.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IPictureService
|
||||||
|
{
|
||||||
|
Task<Stream> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
|
|
||||||
namespace Musify.Application.Contracts.Infrastructure
|
|
||||||
{
|
|
||||||
public interface IStorageHandler
|
|
||||||
{
|
|
||||||
Task<Result<Stream>> GetFileAsync(string bucket, string key, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<string> GetUrlAsync(string bucket, string key, TimeSpan ExpirationTime, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result<string>> UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result<string>> UploadFileAsync(Stream sourceStream, string contentType, string bucket, string key, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result> TransferFilesAsync(string sourceDirectory, string bucket, string route, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result> RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
Task<Result> CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey,
|
|
||||||
CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Musify.Application.Contracts.Infrastructure
|
||||||
|
{
|
||||||
|
public interface IStorageService
|
||||||
|
{
|
||||||
|
Task<Stream> GetFileAsync(string bucket, string key, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<string> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using Musify.Domain.Entities;
|
|||||||
|
|
||||||
namespace Musify.Application.PlayLists.Handlers
|
namespace Musify.Application.PlayLists.Handlers
|
||||||
{
|
{
|
||||||
public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler,
|
public class CreatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageService storageHandler,
|
||||||
ILogger<CreatePlayListCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration, PlayListConfiguration playListConfiguration)
|
ILogger<CreatePlayListCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration, PlayListConfiguration playListConfiguration)
|
||||||
: IRequestHandler<CreatePlayListCommand, Task<Result<PlayListResponse>> >
|
: IRequestHandler<CreatePlayListCommand, Task<Result<PlayListResponse>> >
|
||||||
{
|
{
|
||||||
@@ -80,16 +80,16 @@ namespace Musify.Application.PlayLists.Handlers
|
|||||||
var pictureName = Guid.NewGuid() + picture.FileType;
|
var pictureName = Guid.NewGuid() + picture.FileType;
|
||||||
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName);
|
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName);
|
||||||
|
|
||||||
var uploadResult = await storageHandler.UploadFileAsync(picture.FileStream, picture.ContentType, storageConfiguration.BucketName, pictureKey, cancellationToken);
|
try
|
||||||
if (!uploadResult.IsSuccess)
|
|
||||||
{
|
{
|
||||||
logger.LogError("Failed to upload picture for playlist {PlayListId} to storage: {ErrorMessage}",
|
await storageHandler.UploadFileAsync(picture.FileStream, picture.ContentType, storageConfiguration.BucketName, pictureKey, cancellationToken);
|
||||||
playList.Id, string.Join(", ", uploadResult.Errors));
|
}
|
||||||
return uploadResult.ToErrorResult();
|
catch
|
||||||
|
{
|
||||||
|
return Result.Error($"An error occurred while uploading picture for playlist {playList.Id} to storage.");
|
||||||
}
|
}
|
||||||
|
|
||||||
playList.OriginalPictureName = pictureName;
|
playList.OriginalPictureName = pictureName;
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using Musify.Domain.Entities;
|
|||||||
|
|
||||||
namespace Musify.Application.PlayLists.Handlers
|
namespace Musify.Application.PlayLists.Handlers
|
||||||
{
|
{
|
||||||
public class UpdatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageHandler storageHandler,
|
public class UpdatePlayListCommandHandler(IEventBus eventBus, IDatabase database, IStorageService storageHandler,
|
||||||
ILogger<UpdatePlayListCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration,
|
ILogger<UpdatePlayListCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration,
|
||||||
PlayListConfiguration playListConfiguration)
|
PlayListConfiguration playListConfiguration)
|
||||||
: IRequestHandler<UpdatePlayListCommand, Task<Result<PlayListResponse>> >
|
: IRequestHandler<UpdatePlayListCommand, Task<Result<PlayListResponse>> >
|
||||||
@@ -80,19 +80,20 @@ namespace Musify.Application.PlayLists.Handlers
|
|||||||
|
|
||||||
var pictureName = Guid.NewGuid() + newPicture.FileType;
|
var pictureName = Guid.NewGuid() + newPicture.FileType;
|
||||||
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName);
|
var pictureKey = Path.Combine(playListConfiguration.Routes.OriginalPicturesPath, pictureName);
|
||||||
var uploadResult = await storageHandler.UploadFileAsync(
|
try
|
||||||
|
{
|
||||||
|
await storageHandler.UploadFileAsync(
|
||||||
newPicture.FileStream,
|
newPicture.FileStream,
|
||||||
newPicture.ContentType,
|
newPicture.ContentType,
|
||||||
storageConfiguration.BucketName,
|
storageConfiguration.BucketName,
|
||||||
pictureKey,
|
pictureKey,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
if (!uploadResult.IsSuccess)
|
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Result.Error("Failed to upload picture to storage");
|
||||||
|
}
|
||||||
|
|
||||||
playList.OriginalPictureName = pictureName;
|
playList.OriginalPictureName = pictureName;
|
||||||
return Result.Success(pictureKey);
|
return Result.Success(pictureKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using Musify.Domain.Entities;
|
|||||||
|
|
||||||
namespace Musify.Application.Tracks.Handler
|
namespace Musify.Application.Tracks.Handler
|
||||||
{
|
{
|
||||||
public class CreateTrackCommandHandler(IDatabase database, IEventBus eventBus, IStorageHandler storageHandler,
|
public class CreateTrackCommandHandler(IDatabase database, IEventBus eventBus, IStorageService storageHandler,
|
||||||
ILogger<CreateTrackCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration, TrackConfiguration trackConfiguration)
|
ILogger<CreateTrackCommandHandler> logger, ApplicationStorageConfiguration storageConfiguration, TrackConfiguration trackConfiguration)
|
||||||
: IRequestHandler<CreateTrackCommand, Task<Result<TrackResponse> >>
|
: IRequestHandler<CreateTrackCommand, Task<Result<TrackResponse> >>
|
||||||
{
|
{
|
||||||
@@ -49,33 +49,34 @@ namespace Musify.Application.Tracks.Handler
|
|||||||
string audioKey,
|
string audioKey,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pictureUpload = await storageHandler.UploadFileAsync(
|
try
|
||||||
|
{
|
||||||
|
await storageHandler.UploadFileAsync(
|
||||||
request.Picture.FileStream,
|
request.Picture.FileStream,
|
||||||
request.Picture.ContentType,
|
request.Picture.ContentType,
|
||||||
storageConfiguration.BucketName,
|
storageConfiguration.BucketName,
|
||||||
pictureKey,
|
pictureKey,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
}
|
||||||
if (!pictureUpload.IsSuccess)
|
catch
|
||||||
{
|
{
|
||||||
var errors = string.Join(", ", pictureUpload.Errors);
|
|
||||||
return Result.Error($"Failed to upload picture for {request.Title}");
|
return Result.Error($"Failed to upload picture for {request.Title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioUpload = await storageHandler.UploadFileAsync(
|
try
|
||||||
|
{
|
||||||
|
await storageHandler.UploadFileAsync(
|
||||||
request.Audio.FileStream,
|
request.Audio.FileStream,
|
||||||
request.Audio.ContentType,
|
request.Audio.ContentType,
|
||||||
storageConfiguration.BucketName,
|
storageConfiguration.BucketName,
|
||||||
audioKey,
|
audioKey,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
}
|
||||||
if (!audioUpload.IsSuccess)
|
catch
|
||||||
{
|
{
|
||||||
await RollbackFilesAsync(pictureKey, audioKey, cancellationToken);
|
await RollbackFilesAsync(pictureKey, audioKey, cancellationToken);
|
||||||
var errors = string.Join(", ", audioUpload.Errors);
|
|
||||||
return Result.Error($"Failed to upload audio for {request.Title}");
|
return Result.Error($"Failed to upload audio for {request.Title}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Musify.Domain.Entities;
|
|||||||
|
|
||||||
namespace Musify.Application.Tracks.Handler
|
namespace Musify.Application.Tracks.Handler
|
||||||
{
|
{
|
||||||
public class DeleteTrackCommandHandler(IDatabase database, IStorageHandler storageHandler, ILogger<DeleteTrackCommandHandler> logger,
|
public class DeleteTrackCommandHandler(IDatabase database, IStorageService storageHandler, ILogger<DeleteTrackCommandHandler> logger,
|
||||||
TrackConfiguration trackConfiguration, ApplicationStorageConfiguration storageConfiguration)
|
TrackConfiguration trackConfiguration, ApplicationStorageConfiguration storageConfiguration)
|
||||||
: IRequestHandler<DeleteTrackCommand, Task<Result>>
|
: IRequestHandler<DeleteTrackCommand, Task<Result>>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ namespace Musify.Application.Users.Handlers
|
|||||||
return Result<UserResponse>.Conflict($"User {request.Id} already exists");
|
return Result<UserResponse>.Conflict($"User {request.Id} already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
var keycloakUser = await keycloakUserClient.GetUserByIdAsync(request.Id.ToString(), cancellationToken);
|
KeycloakUserResponse keycloakUser;
|
||||||
if (!keycloakUser.IsSuccess)
|
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");
|
return Result.NotFound("Keycloak user not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,9 +37,9 @@ namespace Musify.Application.Users.Handlers
|
|||||||
logger.LogInformation("Created user {UserId}", newUser.Id);
|
logger.LogInformation("Created user {UserId}", newUser.Id);
|
||||||
return Result.Created(new UserResponse(
|
return Result.Created(new UserResponse(
|
||||||
newUser.Id.ToString(),
|
newUser.Id.ToString(),
|
||||||
keycloakUser.Value.Name,
|
keycloakUser.Name,
|
||||||
keycloakUser.Value.FirstName,
|
keycloakUser.FirstName,
|
||||||
keycloakUser.Value.SecondName,
|
keycloakUser.SecondName,
|
||||||
newUser.CreatedAt,
|
newUser.CreatedAt,
|
||||||
newUser.UpdatedAt));
|
newUser.UpdatedAt));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,21 @@ namespace Musify.Application.Users.Handlers
|
|||||||
if (user is null)
|
if (user is null)
|
||||||
return Result.NotFound("User not found");
|
return Result.NotFound("User not found");
|
||||||
|
|
||||||
var keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken);
|
KeycloakUserResponse keycloakUser;
|
||||||
if (!keycloakUser.IsSuccess)
|
try
|
||||||
return Result.NotFound("Keycloak user not found");
|
{
|
||||||
|
keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Result.Error("Keycloak user not found");
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Success(new UserResponse(
|
return Result.Success(new UserResponse(
|
||||||
user.Id.ToString(),
|
user.Id.ToString(),
|
||||||
keycloakUser.Value.Name,
|
keycloakUser.Name,
|
||||||
keycloakUser.Value.FirstName,
|
keycloakUser.FirstName,
|
||||||
keycloakUser.Value.SecondName,
|
keycloakUser.SecondName,
|
||||||
user.CreatedAt,
|
user.CreatedAt,
|
||||||
user.UpdatedAt));
|
user.UpdatedAt));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,21 @@ namespace Musify.Application.Users.Handlers
|
|||||||
if (user is null)
|
if (user is null)
|
||||||
return Result.NotFound("User not found");
|
return Result.NotFound("User not found");
|
||||||
|
|
||||||
var keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken);
|
KeycloakUserResponse keycloakUser;
|
||||||
if (!keycloakUser.IsSuccess)
|
try
|
||||||
|
{
|
||||||
|
keycloakUser = await keycloakUserClient.GetUserByIdAsync(user.Id.ToString(), cancellationToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
return Result.NotFound("Keycloak user not found");
|
return Result.NotFound("Keycloak user not found");
|
||||||
|
}
|
||||||
|
|
||||||
return Result.Success(new UserResponse(
|
return Result.Success(new UserResponse(
|
||||||
user.Id.ToString(),
|
user.Id.ToString(),
|
||||||
keycloakUser.Value.Name,
|
keycloakUser.Name,
|
||||||
keycloakUser.Value.FirstName,
|
keycloakUser.FirstName,
|
||||||
keycloakUser.Value.SecondName,
|
keycloakUser.SecondName,
|
||||||
user.CreatedAt,
|
user.CreatedAt,
|
||||||
user.UpdatedAt));
|
user.UpdatedAt));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<IAudioTranscoder, AudioTranscoder>();
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<DatabaseConfiguration>(configuration, DatabaseConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<KeycloakConfiguration>(configuration, KeycloakConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<InfrastructureStorageConfiguration>(configuration, InfrastructureStorageConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<MessagingConfiguration>(configuration, MessagingConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<OpenTelemetryConfiguration>(configuration, OpenTelemetryConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<AudioTranscoderConfiguration>(configuration, AudioTranscoderConfiguration.SectionName)
|
|
||||||
.AddValidatedConfiguration<WorkerConfiguration>(configuration, WorkerConfiguration.SectionName);
|
|
||||||
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
static IServiceCollection AddValidatedConfiguration<TConfiguration>(
|
|
||||||
this IServiceCollection serviceDescriptors,
|
|
||||||
IConfiguration configuration,
|
|
||||||
string sectionName)
|
|
||||||
where TConfiguration : class
|
|
||||||
{
|
|
||||||
serviceDescriptors
|
|
||||||
.AddOptionsWithValidateOnStart<TConfiguration>()
|
|
||||||
.Bind(configuration.GetRequiredSection(sectionName))
|
|
||||||
.ValidateDataAnnotations();
|
|
||||||
|
|
||||||
serviceDescriptors.AddSingleton(serviceProvider =>
|
|
||||||
serviceProvider.GetRequiredService<IOptions<TConfiguration>>().Value);
|
|
||||||
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
|
|
||||||
namespace Musify.Infrastructure.Configuration
|
namespace Musify.Infrastructure.Configuration
|
||||||
{
|
{
|
||||||
public class MessagingConfiguration
|
public class MassTransitConfiguration
|
||||||
{
|
{
|
||||||
public const string SectionName = "Messaging";
|
public const string SectionName = "Messaging";
|
||||||
|
|
||||||
@@ -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<KeycloakConfiguration>()
|
|
||||||
.Bind(configuration.GetRequiredSection(KeycloakConfiguration.SectionName))
|
|
||||||
.ValidateDataAnnotations();
|
|
||||||
|
|
||||||
serviceDescriptors.AddScoped(serviceProvider =>
|
|
||||||
{
|
|
||||||
var keycloakConfiguration = serviceProvider.GetRequiredService<KeycloakConfiguration>();
|
|
||||||
|
|
||||||
return new KeycloakClient(
|
|
||||||
keycloakConfiguration.BaseUrl,
|
|
||||||
keycloakConfiguration.Username,
|
|
||||||
keycloakConfiguration.Password);
|
|
||||||
} );
|
|
||||||
serviceDescriptors.AddScoped<IKeycloakUserService, KeycloakUserService>();
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<IEventBus, MassTransitEventBus>()
|
|
||||||
.AddScoped<IProcessTrackingStore, ProcessTrackingStore>();
|
|
||||||
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddInfrastructureWorkerModules(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
serviceDescriptors
|
|
||||||
.AddInfrastructureConfigurations(configuration)
|
|
||||||
.AddDatabase(configuration)
|
|
||||||
.AddStorageHandler()
|
|
||||||
.AddPictureHandler()
|
|
||||||
.AddAudioTranscoder()
|
|
||||||
.AddInfrastructureOpenTelemetry()
|
|
||||||
.AddMassTransitConsumers()
|
|
||||||
.AddScoped<IEventBus, MassTransitEventBus>()
|
|
||||||
.AddScoped<IProcessTrackingStore, ProcessTrackingStore>();
|
|
||||||
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs;
|
|||||||
namespace Musify.Infrastructure.MassTransit.Activities
|
namespace Musify.Infrastructure.MassTransit.Activities
|
||||||
{
|
{
|
||||||
public class TranscodeDashAudioActivity(
|
public class TranscodeDashAudioActivity(
|
||||||
IAudioTranscoder audioTranscoder,
|
IAudioTranscoderService audioTranscoder,
|
||||||
ILogger<TranscodeDashAudioActivity> logger,
|
ILogger<TranscodeDashAudioActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IActivity<TranscodeDashAudioArguments, TranscodeDashAudioLog>
|
: IActivity<TranscodeDashAudioArguments, TranscodeDashAudioLog>
|
||||||
@@ -40,18 +40,10 @@ namespace Musify.Infrastructure.MassTransit.Activities
|
|||||||
{
|
{
|
||||||
await using (var fileStream = File.OpenRead(sourceFilePath))
|
await using (var fileStream = File.OpenRead(sourceFilePath))
|
||||||
{
|
{
|
||||||
var result = await audioTranscoder.TranscodeToDashAsync(
|
await audioTranscoder.TranscodeToDashAsync(
|
||||||
fileStream,
|
fileStream,
|
||||||
workingDirectory,
|
workingDirectory,
|
||||||
executeContext.CancellationToken);
|
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);
|
File.Delete(sourceFilePath);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs;
|
|||||||
namespace Musify.Infrastructure.MassTransit.Activities
|
namespace Musify.Infrastructure.MassTransit.Activities
|
||||||
{
|
{
|
||||||
public class DownloadFileFromBucketActivity(
|
public class DownloadFileFromBucketActivity(
|
||||||
IStorageHandler storageHandler,
|
IStorageService storageHandler,
|
||||||
ILogger<DownloadFileFromBucketActivity> logger,
|
ILogger<DownloadFileFromBucketActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IActivity<DownloadFileFromBucketArguments, DownloadFileFromBucketLog>
|
: IActivity<DownloadFileFromBucketArguments, DownloadFileFromBucketLog>
|
||||||
@@ -36,26 +36,15 @@ namespace Musify.Infrastructure.MassTransit.Activities
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var getFile = await storageHandler.GetFileAsync(
|
using var fileStream = await storageHandler.GetFileAsync(
|
||||||
executeContext.Arguments.Bucket,
|
executeContext.Arguments.Bucket,
|
||||||
executeContext.Arguments.Key,
|
executeContext.Arguments.Key,
|
||||||
executeContext.CancellationToken);
|
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);
|
var destinationDirectory = Path.GetDirectoryName(destinationPath);
|
||||||
if (!string.IsNullOrWhiteSpace(destinationDirectory))
|
if (!string.IsNullOrWhiteSpace(destinationDirectory))
|
||||||
Directory.CreateDirectory(destinationDirectory);
|
Directory.CreateDirectory(destinationDirectory);
|
||||||
|
|
||||||
using var fileStream = getFile.Value;
|
|
||||||
if (fileStream.CanSeek)
|
if (fileStream.CanSeek)
|
||||||
fileStream.Position = 0;
|
fileStream.Position = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using Musify.Infrastructure.MassTransit.Activities.Arguments;
|
|||||||
namespace Musify.Infrastructure.MassTransit.Activities
|
namespace Musify.Infrastructure.MassTransit.Activities
|
||||||
{
|
{
|
||||||
public class RemoveFileFromBucketActivity(
|
public class RemoveFileFromBucketActivity(
|
||||||
IStorageHandler storageHandler,
|
IStorageService storageHandler,
|
||||||
ILogger<RemoveFileFromBucketActivity> logger,
|
ILogger<RemoveFileFromBucketActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IExecuteActivity<RemoveFileFromBucketArguments>
|
: IExecuteActivity<RemoveFileFromBucketArguments>
|
||||||
@@ -32,20 +32,11 @@ namespace Musify.Infrastructure.MassTransit.Activities
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var removeFile = await storageHandler.RemoveFileAsync(
|
await storageHandler.RemoveFileAsync(
|
||||||
executeContext.Arguments.Bucket,
|
executeContext.Arguments.Bucket,
|
||||||
executeContext.Arguments.Key,
|
executeContext.Arguments.Key,
|
||||||
executeContext.CancellationToken);
|
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);
|
await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
|
||||||
return executeContext.Completed();
|
return executeContext.Completed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Activities.Logs;
|
|||||||
namespace Musify.Infrastructure.MassTransit.Activities
|
namespace Musify.Infrastructure.MassTransit.Activities
|
||||||
{
|
{
|
||||||
public class TransferFilesToBucketActivity(
|
public class TransferFilesToBucketActivity(
|
||||||
IStorageHandler storageHandler,
|
IStorageService storageHandler,
|
||||||
ILogger<TransferFilesToBucketActivity> logger,
|
ILogger<TransferFilesToBucketActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IActivity<TransferFilesToBucketArguments, TransferFilesToBucketLog>
|
: IActivity<TransferFilesToBucketArguments, TransferFilesToBucketLog>
|
||||||
@@ -42,23 +42,12 @@ namespace Musify.Infrastructure.MassTransit.Activities
|
|||||||
.Select(file => Path.Combine(destinationKey, Path.GetFileName(file)))
|
.Select(file => Path.Combine(destinationKey, Path.GetFileName(file)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var transferFilesResult = await storageHandler.TransferFilesAsync(
|
await storageHandler.TransferFilesAsync(
|
||||||
folderPath,
|
folderPath,
|
||||||
executeContext.Arguments.DestinationBucket,
|
executeContext.Arguments.DestinationBucket,
|
||||||
destinationKey,
|
destinationKey,
|
||||||
executeContext.CancellationToken);
|
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);
|
await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
|
||||||
return executeContext.Completed();
|
return executeContext.Completed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using Musify.Infrastructure.MassTransit.Logs;
|
|||||||
|
|
||||||
namespace Musify.Infrastructure.MassTransit.Activities.Files
|
namespace Musify.Infrastructure.MassTransit.Activities.Files
|
||||||
{
|
{
|
||||||
public class UploadFileToBucketActivity(IStorageHandler storageHandler,
|
public class UploadFileToBucketActivity(IStorageService storageHandler,
|
||||||
ILogger<UploadFileToBucketActivity> logger,
|
ILogger<UploadFileToBucketActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IActivity<UploadFileToBucketArguments, UploadFileToBucketLog>
|
: IActivity<UploadFileToBucketArguments, UploadFileToBucketLog>
|
||||||
@@ -37,22 +37,12 @@ namespace Musify.Infrastructure.MassTransit.Activities.Files
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uploadResult = await storageHandler.UploadFileAsync(
|
await storageHandler.UploadFileAsync(
|
||||||
sourceFilePath,
|
sourceFilePath,
|
||||||
executeContext.Arguments.DestinationBucket,
|
executeContext.Arguments.DestinationBucket,
|
||||||
Path.Combine(executeContext.Arguments.DestinationRoute, fileName),
|
Path.Combine(executeContext.Arguments.DestinationRoute, fileName),
|
||||||
executeContext.CancellationToken);
|
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);
|
await processTrackingStore.CompleteStepAsync(processId, stepId, executeContext.CancellationToken);
|
||||||
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
|
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
|
||||||
executeContext.Arguments.DestinationBucket,
|
executeContext.Arguments.DestinationBucket,
|
||||||
@@ -70,19 +60,10 @@ namespace Musify.Infrastructure.MassTransit.Activities.Files
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uploadResult = await storageHandler.RemoveFileAsync(
|
await storageHandler.RemoveFileAsync(
|
||||||
compensateContext.Log.DestinationBucket,
|
compensateContext.Log.DestinationBucket,
|
||||||
compensateContext.Log.DestinationKey,
|
compensateContext.Log.DestinationKey,
|
||||||
compensateContext.CancellationToken);
|
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();
|
return compensateContext.Compensated();
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Musify.Infrastructure.MassTransit.RoutingSlip.Builders;
|
|||||||
namespace Musify.Infrastructure.MassTransit.Activities
|
namespace Musify.Infrastructure.MassTransit.Activities
|
||||||
{
|
{
|
||||||
public class ResizePictureActivity(
|
public class ResizePictureActivity(
|
||||||
IPictureHandler pictureHandler,
|
IPictureService pictureHandler,
|
||||||
ILogger<ResizePictureActivity> logger,
|
ILogger<ResizePictureActivity> logger,
|
||||||
IProcessTrackingStore processTrackingStore)
|
IProcessTrackingStore processTrackingStore)
|
||||||
: IActivity<ResizePictureLocalArguments, ResizePictureLog>
|
: IActivity<ResizePictureLocalArguments, ResizePictureLog>
|
||||||
@@ -53,23 +53,12 @@ namespace Musify.Infrastructure.MassTransit.Activities
|
|||||||
executeContext.Arguments.Height,
|
executeContext.Arguments.Height,
|
||||||
executeContext.CancellationToken);
|
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);
|
var destinationDirectory = Path.GetDirectoryName(destinationFilePath);
|
||||||
if (!string.IsNullOrEmpty(destinationDirectory))
|
if (!string.IsNullOrEmpty(destinationDirectory))
|
||||||
Directory.CreateDirectory(destinationDirectory);
|
Directory.CreateDirectory(destinationDirectory);
|
||||||
|
|
||||||
await using var destinationStream = File.Create(destinationFilePath);
|
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}",
|
logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}",
|
||||||
sourceFilePath,
|
sourceFilePath,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Musify.Infrastructure.MassTransit
|
|||||||
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
|
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
|
||||||
ConfigureRabbitMqHost(
|
ConfigureRabbitMqHost(
|
||||||
busFactoryConfigurator,
|
busFactoryConfigurator,
|
||||||
busRegistrationContext.GetRequiredService<MessagingConfiguration>()));
|
busRegistrationContext.GetRequiredService<MassTransitConfiguration>()));
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return serviceDescriptors;
|
return serviceDescriptors;
|
||||||
@@ -60,7 +60,7 @@ namespace Musify.Infrastructure.MassTransit
|
|||||||
{
|
{
|
||||||
ConfigureRabbitMqHost(
|
ConfigureRabbitMqHost(
|
||||||
busFactoryConfigurator,
|
busFactoryConfigurator,
|
||||||
busRegistrationContext.GetRequiredService<MessagingConfiguration>());
|
busRegistrationContext.GetRequiredService<MassTransitConfiguration>());
|
||||||
|
|
||||||
busFactoryConfigurator.UseConsumeFilter(typeof(ProcessTrackingConsumeFilter<>), busRegistrationContext);
|
busFactoryConfigurator.UseConsumeFilter(typeof(ProcessTrackingConsumeFilter<>), busRegistrationContext);
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ namespace Musify.Infrastructure.MassTransit
|
|||||||
|
|
||||||
static void ConfigureRabbitMqHost(
|
static void ConfigureRabbitMqHost(
|
||||||
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
|
IRabbitMqBusFactoryConfigurator busFactoryConfigurator,
|
||||||
MessagingConfiguration massTransitConfiguration)
|
MassTransitConfiguration massTransitConfiguration)
|
||||||
{
|
{
|
||||||
busFactoryConfigurator.Host(massTransitConfiguration.Host, options =>
|
busFactoryConfigurator.Host(massTransitConfiguration.Host, options =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Musify.Application.Contracts.Infrastructure;
|
using Musify.Application.Contracts.Infrastructure;
|
||||||
|
using Musify.Infrastructure.Configuration;
|
||||||
|
|
||||||
namespace Musify.Infrastructure.Persistence
|
namespace Musify.Infrastructure.Persistence
|
||||||
{
|
{
|
||||||
@@ -8,6 +10,14 @@ namespace Musify.Infrastructure.Persistence
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddDatabase(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
public static IServiceCollection AddDatabase(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
serviceDescriptors
|
||||||
|
.AddOptionsWithValidateOnStart<DatabaseConfiguration>()
|
||||||
|
.Bind(configuration.GetRequiredSection(DatabaseConfiguration.SectionName))
|
||||||
|
.ValidateDataAnnotations();
|
||||||
|
|
||||||
|
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<IOptions<DatabaseConfiguration>>().Value);
|
||||||
|
|
||||||
serviceDescriptors.AddDbContext<Database>();
|
serviceDescriptors.AddDbContext<Database>();
|
||||||
serviceDescriptors.AddScoped<IDatabase>(serviceProvider => serviceProvider.GetRequiredService<Database>());
|
serviceDescriptors.AddScoped<IDatabase>(serviceProvider => serviceProvider.GetRequiredService<Database>());
|
||||||
|
|
||||||
|
|||||||
@@ -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<IPictureHandler, PictureHandler>();
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+18
-18
@@ -1,17 +1,18 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Musify.Application.Contracts.Infrastructure;
|
using Musify.Application.Contracts.Infrastructure;
|
||||||
using Musify.Infrastructure.Configuration;
|
using Musify.Infrastructure.Configuration;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Musify.Infrastructure.Audio
|
namespace Musify.Infrastructure.Services
|
||||||
{
|
{
|
||||||
public class AudioTranscoder(
|
public class AudioTranscoderService(
|
||||||
ILogger<AudioTranscoder> logger,
|
ILogger<AudioTranscoderService> logger,
|
||||||
AudioTranscoderConfiguration audioTranscoderConfiguration)
|
AudioTranscoderConfiguration audioTranscoderConfiguration)
|
||||||
: IAudioTranscoder
|
: IAudioTranscoderService
|
||||||
{
|
{
|
||||||
public async Task<Result> TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken)
|
record FfmpegExecutionResult(int ExitCode, string StandardOutput, string StandardError);
|
||||||
|
|
||||||
|
public async Task<int> TranscodeToDashAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -30,30 +31,32 @@ namespace Musify.Infrastructure.Audio
|
|||||||
{
|
{
|
||||||
logger.LogError("Audio transcoding to DASH failed with exit code {ExitCode}. Output: {StandardOutput}, Error: {StandardError}",
|
logger.LogError("Audio transcoding to DASH failed with exit code {ExitCode}. Output: {StandardOutput}, Error: {StandardError}",
|
||||||
executionResult.ExitCode, executionResult.StandardOutput, executionResult.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 executionResult.ExitCode;
|
||||||
return Result.Success();
|
|
||||||
}
|
}
|
||||||
catch (TimeoutException timeoutException)
|
catch (TimeoutException timeoutException)
|
||||||
{
|
{
|
||||||
logger.LogError(timeoutException, "Audio transcoding timed out after {Timeout} for {DestinationPath}", audioTranscoderConfiguration.TranscodingTimeout, destinationPath);
|
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)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError(exception, "Failed to transcode audio to DASH for {DestinationPath}", destinationPath);
|
logger.LogError(exception, "Failed to transcode audio to DASH for {DestinationPath}", destinationPath);
|
||||||
return Result.Error(exception.Message);
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<bool>> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken)
|
public async Task<bool> IsValidAudioFileAsync(string filePath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
{
|
{
|
||||||
logger.LogWarning("Audio validation requested for non-existing file {FilePath}", filePath);
|
logger.LogWarning("Audio validation requested for non-existing file {FilePath}", filePath);
|
||||||
return Result<bool>.Error($"Audio file not found: {filePath}");
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -76,18 +79,17 @@ namespace Musify.Infrastructure.Audio
|
|||||||
logger.LogDebug("Audio validation successful for {FilePath}", filePath);
|
logger.LogDebug("Audio validation successful for {FilePath}", filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result<bool>.Success(isValid);
|
return isValid;
|
||||||
}
|
}
|
||||||
catch (TimeoutException timeoutException)
|
catch (TimeoutException timeoutException)
|
||||||
{
|
{
|
||||||
logger.LogError(timeoutException, "Audio validation timed out for {FilePath}", filePath);
|
logger.LogError(timeoutException, "Audio validation timed out for {FilePath}", filePath);
|
||||||
return Result<bool>.Error("Audio validation timed out.");
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError(exception, "Failed to validate audio file {FilePath}", filePath);
|
logger.LogError(exception, "Failed to validate audio file {FilePath}", filePath);
|
||||||
return Result<bool>.Error(exception.Message);
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<FfmpegExecutionResult> ExecuteFfmpegWithInputAsync(
|
async Task<FfmpegExecutionResult> ExecuteFfmpegWithInputAsync(
|
||||||
@@ -216,7 +218,5 @@ namespace Musify.Infrastructure.Audio
|
|||||||
|
|
||||||
static string BuildValidateAudioArguments(string filePath)
|
static string BuildValidateAudioArguments(string filePath)
|
||||||
=> $"-v error -i \"{filePath}\" -map 0:a:0 -f null -";
|
=> $"-v error -i \"{filePath}\" -map 0:a:0 -f null -";
|
||||||
|
|
||||||
sealed record FfmpegExecutionResult(int ExitCode, string StandardOutput, string StandardError);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+15
-7
@@ -1,4 +1,3 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
using Keycloak.Net;
|
using Keycloak.Net;
|
||||||
using Keycloak.Net.Models.Users;
|
using Keycloak.Net.Models.Users;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -6,14 +5,14 @@ using Musify.Application.Contracts.Infrastructure;
|
|||||||
using Musify.Application.Users.Responses;
|
using Musify.Application.Users.Responses;
|
||||||
using Musify.Infrastructure.Configuration;
|
using Musify.Infrastructure.Configuration;
|
||||||
|
|
||||||
namespace Musify.Infrastructure.Identity
|
namespace Musify.Infrastructure.Services
|
||||||
{
|
{
|
||||||
public class KeycloakUserService(
|
public class KeycloakUserService(
|
||||||
KeycloakClient keycloakClient,
|
KeycloakClient keycloakClient,
|
||||||
KeycloakConfiguration keycloakConfiguration,
|
KeycloakConfiguration keycloakConfiguration,
|
||||||
ILogger<KeycloakUserService> logger) : IKeycloakUserService
|
ILogger<KeycloakUserService> logger) : IKeycloakUserService
|
||||||
{
|
{
|
||||||
public async Task<Result<KeycloakUserResponse>> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken)
|
public async Task<KeycloakUserResponse> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
User keycloakUser;
|
User keycloakUser;
|
||||||
|
|
||||||
@@ -24,14 +23,14 @@ namespace Musify.Infrastructure.Identity
|
|||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Operation to retrieve Keycloak user {KeycloakId} was cancelled", keycloakId);
|
logger.LogWarning("Operation to retrieve Keycloak user {KeycloakId} was cancelled", keycloakId);
|
||||||
return Result.Error("Operation cancelled");
|
throw;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogWarning(exception, "Failed to retrieve Keycloak user {KeycloakId}", keycloakId);
|
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<IReadOnlyCollection<KeycloakUserResponse>> GetUsersAsync(CancellationToken cancellationToken, string search = "", int first = 0, int max = 20, string username = "")
|
public async Task<IReadOnlyCollection<KeycloakUserResponse>> GetUsersAsync(CancellationToken cancellationToken, string search = "", int first = 0, int max = 20, string username = "")
|
||||||
@@ -45,7 +44,7 @@ namespace Musify.Infrastructure.Identity
|
|||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
return users
|
return users
|
||||||
.Select(KeycloakUserMapper.Map)
|
.Select(ToResponse)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,5 +56,14 @@ namespace Musify.Infrastructure.Identity
|
|||||||
username: username,
|
username: username,
|
||||||
cancellationToken: cancellationToken);
|
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."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-10
@@ -1,15 +1,14 @@
|
|||||||
using Ardalis.Result;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Musify.Application.Contracts.Infrastructure;
|
using Musify.Application.Contracts.Infrastructure;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
|
||||||
namespace Musify.Infrastructure.Pictures
|
namespace Musify.Infrastructure.Services
|
||||||
{
|
{
|
||||||
public class PictureHandler(ILogger<PictureHandler> logger)
|
public class PictureService(ILogger<PictureService> logger)
|
||||||
: IPictureHandler
|
: IPictureService
|
||||||
{
|
{
|
||||||
public async Task<Result<Stream>> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken)
|
public async Task<Stream> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -18,7 +17,6 @@ namespace Musify.Infrastructure.Pictures
|
|||||||
pictureStream.Position = 0;
|
pictureStream.Position = 0;
|
||||||
|
|
||||||
using var picture = await Image.LoadAsync(pictureStream, cancellationToken);
|
using var picture = await Image.LoadAsync(pictureStream, cancellationToken);
|
||||||
|
|
||||||
picture.Mutate(options => options.Resize(new ResizeOptions
|
picture.Mutate(options => options.Resize(new ResizeOptions
|
||||||
{
|
{
|
||||||
Size = new Size(width, height),
|
Size = new Size(width, height),
|
||||||
@@ -26,18 +24,16 @@ namespace Musify.Infrastructure.Pictures
|
|||||||
} ));
|
} ));
|
||||||
|
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
|
|
||||||
await picture.SaveAsWebpAsync(memoryStream, cancellationToken);
|
await picture.SaveAsWebpAsync(memoryStream, cancellationToken);
|
||||||
|
|
||||||
memoryStream.Position = 0;
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
logger.LogDebug("Picture resized to {Width}x{Height}, length: {Length}", width, height, memoryStream.Length);
|
logger.LogDebug("Picture resized to {Width}x{Height}, length: {Length}", width, height, memoryStream.Length);
|
||||||
return Result<Stream>.Success(memoryStream);
|
return memoryStream;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError(exception, "Failed to resize picture to {Width}x{Height}", width, height);
|
logger.LogError(exception, "Failed to resize picture to {Width}x{Height}", width, height);
|
||||||
return Result.Error("Failed to resize picture");
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<InfrastructureStorageConfiguration>()
|
||||||
|
.Bind(configuration.GetRequiredSection(InfrastructureStorageConfiguration.SectionName))
|
||||||
|
.ValidateDataAnnotations();
|
||||||
|
|
||||||
|
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<IOptions<InfrastructureStorageConfiguration>>().Value);
|
||||||
|
|
||||||
|
serviceDescriptors.AddScoped<IAmazonS3>(serviceProvider =>
|
||||||
|
{
|
||||||
|
var configuration = serviceProvider.GetRequiredService<InfrastructureStorageConfiguration>();
|
||||||
|
var s3Configuration = new AmazonS3Config
|
||||||
|
{
|
||||||
|
ServiceURL = configuration.ServiceUrl,
|
||||||
|
ForcePathStyle = configuration.ForcePathStyle,
|
||||||
|
UseHttp = configuration.UseHttp,
|
||||||
|
};
|
||||||
|
return new AmazonS3Client(configuration.AccessKey, configuration.SecretAccessKey, s3Configuration);
|
||||||
|
} );
|
||||||
|
|
||||||
|
serviceDescriptors.AddScoped<IStorageService, StorageService>();
|
||||||
|
return serviceDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddPictureService(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
serviceDescriptors.AddScoped<IPictureService, PictureService>();
|
||||||
|
return serviceDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddKeycloakService(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
serviceDescriptors.AddOptionsWithValidateOnStart<KeycloakConfiguration>()
|
||||||
|
.Bind(configuration.GetRequiredSection(KeycloakConfiguration.SectionName))
|
||||||
|
.ValidateDataAnnotations();
|
||||||
|
|
||||||
|
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<IOptions<KeycloakConfiguration>>().Value);
|
||||||
|
|
||||||
|
serviceDescriptors.AddScoped(serviceProvider =>
|
||||||
|
{
|
||||||
|
var keycloakConfiguration = serviceProvider.GetRequiredService<KeycloakConfiguration>();
|
||||||
|
|
||||||
|
return new KeycloakClient(
|
||||||
|
keycloakConfiguration.BaseUrl,
|
||||||
|
keycloakConfiguration.Username,
|
||||||
|
keycloakConfiguration.Password);
|
||||||
|
} );
|
||||||
|
serviceDescriptors.AddScoped<IKeycloakUserService, KeycloakUserService>();
|
||||||
|
return serviceDescriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAudioTranscoder(this IServiceCollection serviceDescriptors, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
serviceDescriptors
|
||||||
|
.AddOptionsWithValidateOnStart<AudioTranscoderConfiguration>()
|
||||||
|
.Bind(configuration.GetRequiredSection(AudioTranscoderConfiguration.SectionName))
|
||||||
|
.ValidateDataAnnotations();
|
||||||
|
|
||||||
|
serviceDescriptors.AddSingleton(serviceProvider =>
|
||||||
|
serviceProvider.GetRequiredService<IOptions<AudioTranscoderConfiguration>>().Value);
|
||||||
|
|
||||||
|
serviceDescriptors.AddScoped<IAudioTranscoderService, AudioTranscoderService>();
|
||||||
|
return serviceDescriptors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-29
@@ -1,19 +1,18 @@
|
|||||||
using Amazon.S3;
|
using Amazon.S3;
|
||||||
using Amazon.S3.Model;
|
using Amazon.S3.Model;
|
||||||
using Amazon.S3.Transfer;
|
using Amazon.S3.Transfer;
|
||||||
using Ardalis.Result;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MimeMapping;
|
using MimeMapping;
|
||||||
using Musify.Application.Contracts.Infrastructure;
|
using Musify.Application.Contracts.Infrastructure;
|
||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Infrastructure.Configuration;
|
using Musify.Infrastructure.Configuration;
|
||||||
|
|
||||||
namespace Musify.Infrastructure.Storage
|
namespace Musify.Infrastructure.Services
|
||||||
{
|
{
|
||||||
public class StorageHandler(IDatabase database, IAmazonS3 amazonS3, InfrastructureStorageConfiguration storageClientConfiguration, ILogger<StorageHandler> logger)
|
public class StorageService(IDatabase database, IAmazonS3 amazonS3, InfrastructureStorageConfiguration storageClientConfiguration, ILogger<StorageService> logger)
|
||||||
: IStorageHandler
|
: IStorageService
|
||||||
{
|
{
|
||||||
public async Task<Result<Stream>> GetFileAsync(string bucket, string key, CancellationToken cancellationToken)
|
public async Task<Stream> GetFileAsync(string bucket, string key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new GetObjectRequest
|
var request = new GetObjectRequest
|
||||||
{
|
{
|
||||||
@@ -24,14 +23,13 @@ namespace Musify.Infrastructure.Storage
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await amazonS3.GetObjectAsync(request, cancellationToken);
|
var response = await amazonS3.GetObjectAsync(request, cancellationToken);
|
||||||
|
|
||||||
logger.LogDebug("Retrieved file from S3 {Bucket}/{Key}", bucket, key);
|
logger.LogDebug("Retrieved file from S3 {Bucket}/{Key}", bucket, key);
|
||||||
return Result.Success(response.ResponseStream);
|
return response.ResponseStream;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogWarning(exception, "Failed to get file from S3 {Bucket}/{Key}", bucket, key);
|
logger.LogError(exception, "Failed to get file from S3 {Bucket}/{Key}", bucket, key);
|
||||||
return Result.NotFound($"File {bucket}/{key} not found");
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,12 +44,10 @@ namespace Musify.Infrastructure.Storage
|
|||||||
};
|
};
|
||||||
|
|
||||||
logger.LogDebug("Generating pre-signed URL for {Bucket}/{Key}", bucket, key);
|
logger.LogDebug("Generating pre-signed URL for {Bucket}/{Key}", bucket, key);
|
||||||
var url = await amazonS3.GetPreSignedURLAsync(request);
|
return await amazonS3.GetPreSignedURLAsync(request);
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken)
|
public async Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new DeleteObjectRequest
|
var request = new DeleteObjectRequest
|
||||||
{
|
{
|
||||||
@@ -60,12 +56,10 @@ namespace Musify.Infrastructure.Storage
|
|||||||
};
|
};
|
||||||
|
|
||||||
logger.LogDebug("Removing file from S3 {Bucket}/{Key}", bucket, key);
|
logger.LogDebug("Removing file from S3 {Bucket}/{Key}", bucket, key);
|
||||||
var response = await amazonS3.DeleteObjectAsync(request, cancellationToken);
|
await amazonS3.DeleteObjectAsync(request, cancellationToken);
|
||||||
|
|
||||||
return Result.NoContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> 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);
|
var trasnsferUtility = new TransferUtility(amazonS3);
|
||||||
|
|
||||||
@@ -101,31 +95,30 @@ namespace Musify.Infrastructure.Storage
|
|||||||
failedCount++;
|
failedCount++;
|
||||||
upload.State = UploadState.Failed;
|
upload.State = UploadState.Failed;
|
||||||
logger.LogError(exception, "Failed to transfer file to S3 {Bucket}/{Key}", bucket, key);
|
logger.LogError(exception, "Failed to transfer file to S3 {Bucket}/{Key}", bucket, key);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await database.SaveChangesAsync(cancellationToken);
|
await database.SaveChangesAsync(cancellationToken);
|
||||||
logger.LogInformation("File transfer completed to {Bucket}/{Route}. Success: {SuccessCount}, Failed: {FailedCount}", bucket, route, successCount, failedCount);
|
logger.LogInformation("File transfer completed to {Bucket}/{Route}. Success: {SuccessCount}, Failed: {FailedCount}", bucket, route, successCount, failedCount);
|
||||||
|
|
||||||
return Result.Success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<string>> UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken)
|
public async Task UploadFileAsync(string filePath, string bucket, string key, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var fileStream = File.OpenRead(filePath);
|
using var fileStream = File.OpenRead(filePath);
|
||||||
var contentType = MimeUtility.GetMimeMapping(filePath);
|
var contentType = MimeUtility.GetMimeMapping(filePath);
|
||||||
return await UploadFileAsync(fileStream, contentType, bucket, key, cancellationToken);
|
await UploadFileAsync(fileStream, contentType, bucket, key, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError(exception, "Failed to upload file to S3 {Bucket}/{Key}", bucket, key);
|
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<Result<string>> 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
|
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);
|
logger.LogError(exception, "Failed to upload file to S3 {Bucket}/{Key}", bucket, key);
|
||||||
upload.State = UploadState.Failed;
|
upload.State = UploadState.Failed;
|
||||||
return Result.Error($"Failed to upload to {bucket}/{key}");
|
await database.SaveChangesAsync(cancellationToken);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
upload.State = UploadState.Successful;
|
upload.State = UploadState.Successful;
|
||||||
await database.SaveChangesAsync(cancellationToken);
|
await database.SaveChangesAsync(cancellationToken);
|
||||||
logger.LogDebug("Saved upload record for {Bucket}/{Key}", bucket, key);
|
logger.LogDebug("Saved upload record for {Bucket}/{Key}", bucket, key);
|
||||||
|
|
||||||
return Result.Success(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey,
|
public async Task CopyFileAsync(string sourceBucket, string sourceKey, string destinationBucket, string destinationKey,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = new CopyObjectRequest
|
var request = new CopyObjectRequest
|
||||||
@@ -179,13 +171,12 @@ namespace Musify.Infrastructure.Storage
|
|||||||
var response = await amazonS3.CopyObjectAsync(request, cancellationToken);
|
var response = await amazonS3.CopyObjectAsync(request, cancellationToken);
|
||||||
logger.LogInformation("Copied file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
|
logger.LogInformation("Copied file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
|
||||||
sourceBucket, sourceKey, destinationBucket, destinationKey);
|
sourceBucket, sourceKey, destinationBucket, destinationKey);
|
||||||
return Result.Success();
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
logger.LogError(exception, "Failed to copy file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
|
logger.LogError(exception, "Failed to copy file from {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}",
|
||||||
sourceBucket, sourceKey, destinationBucket, destinationKey);
|
sourceBucket, sourceKey, destinationBucket, destinationKey);
|
||||||
return Result.Error($"Failed to copy from {sourceBucket}/{sourceKey} to {destinationBucket}/{destinationKey}");
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<IAmazonS3>(serviceProvider =>
|
|
||||||
{
|
|
||||||
var configuration = serviceProvider.GetRequiredService<InfrastructureStorageConfiguration>();
|
|
||||||
var s3Configuration = new AmazonS3Config
|
|
||||||
{
|
|
||||||
ServiceURL = configuration.ServiceUrl,
|
|
||||||
ForcePathStyle = configuration.ForcePathStyle,
|
|
||||||
UseHttp = configuration.UseHttp,
|
|
||||||
};
|
|
||||||
return new AmazonS3Client(configuration.AccessKey, configuration.SecretAccessKey, s3Configuration);
|
|
||||||
} );
|
|
||||||
|
|
||||||
serviceDescriptors.AddScoped<IStorageHandler, StorageHandler>();
|
|
||||||
return serviceDescriptors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user