First Commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
namespace Musify.Infrastructure.Identity
|
||||
{
|
||||
public class KeycloakConfiguration
|
||||
{
|
||||
public const string SectionName = "Keycloak";
|
||||
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
|
||||
public string Realm { get; set; } = string.Empty;
|
||||
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Ardalis.Result;
|
||||
using Keycloak.Net;
|
||||
using Keycloak.Net.Models.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Application.Users.Contracts;
|
||||
|
||||
namespace Musify.Infrastructure.Identity
|
||||
{
|
||||
public class KeycloakIdentityService(
|
||||
KeycloakClient keycloakClient,
|
||||
KeycloakConfiguration keycloakConfiguration,
|
||||
ILogger<KeycloakIdentityService> logger) : IKeycloakUserClient
|
||||
{
|
||||
public async Task<Result<KeycloakUserResponse>> GetUserByIdAsync(string keycloakId, CancellationToken cancellationToken)
|
||||
{
|
||||
User keycloakUser;
|
||||
|
||||
try
|
||||
{
|
||||
keycloakUser = await keycloakClient.GetUserAsync(keycloakConfiguration.Realm, keycloakId, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogWarning(exception, "Keycloak user with id {KeycloakId} could not be retrieved.", keycloakId);
|
||||
|
||||
return Result.NotFound();
|
||||
}
|
||||
|
||||
return Result.Success(KeycloakUserMapper.Map(keycloakUser));
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<KeycloakUserResponse>> GetUsersAsync(CancellationToken cancellationToken, string search = "", int first = 0, int max = 20, string username = "")
|
||||
{
|
||||
var users = await keycloakClient.GetUsersAsync(
|
||||
realm: keycloakConfiguration.Realm,
|
||||
first: first,
|
||||
max: max,
|
||||
search: search,
|
||||
username: username,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
return users
|
||||
.Select(KeycloakUserMapper.Map)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public Task<int> GetUsersCountAsync(CancellationToken cancellationToken, string search = "", string username = "")
|
||||
{
|
||||
return keycloakClient.GetUsersCountAsync(
|
||||
keycloakConfiguration.Realm,
|
||||
search: search,
|
||||
username: username,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Keycloak.Net.Models.Users;
|
||||
using Musify.Application.Users.Contracts;
|
||||
|
||||
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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Musify.Infrastructure</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AWSSDK.S3" Version="4.0.19" />
|
||||
<PackageReference Include="Keycloak.Net.Core" Version="1.0.38" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Application\Application.csproj" />
|
||||
<ProjectReference Include="..\Musify.Backend\Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
using Musify.Domain.Entities;
|
||||
|
||||
namespace Musify.Infrastructure.Persistence
|
||||
{
|
||||
public class Database(DbContextOptions<Database> dbContextOptions)
|
||||
: DbContext(dbContextOptions), IDatabase
|
||||
{
|
||||
public DbSet<User> Users => Set<User>();
|
||||
|
||||
public DbSet<Track> Tracks => Set<Track>();
|
||||
|
||||
public DbSet<PlayList> PlayLists => Set<PlayList>();
|
||||
|
||||
public DbSet<PlayListHasTrack> PlayListHasTracks => Set<PlayListHasTrack>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Amazon.S3;
|
||||
using Amazon.S3.Model;
|
||||
using Amazon.S3.Transfer;
|
||||
using Ardalis.Result;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Contracts.Infrastructure;
|
||||
|
||||
namespace Musify.Infrastructure.Storage
|
||||
{
|
||||
public class StorageHandler(IAmazonS3 amazonS3, ILogger<StorageHandler> logger)
|
||||
: IStorageHandler
|
||||
{
|
||||
public async Task<Result<Stream>> GetFile(string bucketName, string keyName, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new GetObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = keyName,
|
||||
};
|
||||
|
||||
logger.LogDebug("Getting file from S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await amazonS3.GetObjectAsync(request, cancellationToken);
|
||||
|
||||
logger.LogDebug("Successfully got file from S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
return Result.Success(response.ResponseStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogDebug("Failed to get file from S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
return Result.NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetUrl(string bucketName, string keyName, TimeSpan expirationTime, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new GetPreSignedUrlRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = keyName,
|
||||
Expires = DateTime.UtcNow + expirationTime,
|
||||
Protocol = Protocol.HTTP
|
||||
};
|
||||
|
||||
logger.LogDebug("Getting pre-signed URL from S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
var url = await amazonS3.GetPreSignedURLAsync(request);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public async Task<Result> RemoveFile(string bucketName, string keyName, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new DeleteObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
Key = keyName,
|
||||
};
|
||||
|
||||
logger.LogDebug("Removing file from S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
var response = await amazonS3.DeleteObjectAsync(request, cancellationToken);
|
||||
|
||||
return Result.NoContent();
|
||||
}
|
||||
|
||||
public async Task<Result> TransferFiles(string sourceDirectory, string bucketName, string route, CancellationToken cancellationToken)
|
||||
{
|
||||
var trasnsferUtility = new TransferUtility(amazonS3);
|
||||
|
||||
var files = Directory.GetFiles(sourceDirectory, "*");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
string filePath = Path.Combine(sourceDirectory, file);
|
||||
|
||||
try
|
||||
{
|
||||
await trasnsferUtility.UploadAsync(filePath, bucketName, fileName, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError("Failed to transfer file {FileName} to S3 with bucket name {BucketName} and route {Route}", fileName, bucketName, route);
|
||||
return Result.Error($"Failed to transfer file {fileName} to S3 with bucket name {bucketName} and route {route}");
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public async Task<Result<string>> UploadFile(Stream sourceStream, string contentType, string bucketName, string keyName, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new PutObjectRequest
|
||||
{
|
||||
BucketName = bucketName,
|
||||
ContentType = contentType,
|
||||
InputStream = sourceStream,
|
||||
Key = keyName
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await amazonS3.PutObjectAsync(request, cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
logger.LogError("Failed to upload file to S3 with bucket name {BucketName} and key name {KeyName}", bucketName, keyName);
|
||||
return Result.Error($"Failed to upload file to S3 with bucket name {bucketName} and key name {keyName}");
|
||||
}
|
||||
|
||||
return Result.Success(keyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user