mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Add SeaweedFS-backed file storage infrastructure
Introduces IFileStorageProvider and an S3-compatible implementation (S3FileStorageProvider) configured via new SEAWEEDFS_* configuration keys, for storing uploaded files against SeaweedFS.
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
<Project Path="Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj" />
|
<Project Path="Infrastructure/Infrastructure.Configuration/Infrastructure.Configuration.csproj" />
|
||||||
<Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj" />
|
<Project Path="Infrastructure/Infrastructure.Email/Infrastructure.Email.csproj" />
|
||||||
<Project Path="Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj" />
|
<Project Path="Infrastructure/Infrastructure.Email.Templates/Infrastructure.Email.Templates.csproj" />
|
||||||
|
<Project Path="Infrastructure/Infrastructure.FileUpload/Infrastructure.FileUpload.csproj" />
|
||||||
<Project Path="Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj" />
|
<Project Path="Infrastructure/Infrastructure.Jwt/Infrastructure.Jwt.csproj" />
|
||||||
<Project Path="Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj" />
|
<Project Path="Infrastructure/Infrastructure.PasswordHashing/Infrastructure.PasswordHashing.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ public static class ConfigurationKeys
|
|||||||
public const string SmtpFromEmail = "SMTP_FROM_EMAIL";
|
public const string SmtpFromEmail = "SMTP_FROM_EMAIL";
|
||||||
public const string SmtpFromName = "SMTP_FROM_NAME";
|
public const string SmtpFromName = "SMTP_FROM_NAME";
|
||||||
|
|
||||||
|
public const string SeaweedFsServiceUrl = "SEAWEEDFS_SERVICE_URL";
|
||||||
|
public const string SeaweedFsAccessKeyId = "SEAWEEDFS_ACCESS_KEY_ID";
|
||||||
|
public const string SeaweedFsSecretAccessKey = "SEAWEEDFS_SECRET_ACCESS_KEY";
|
||||||
|
public const string SeaweedFsBucket = "SEAWEEDFS_BUCKET";
|
||||||
|
|
||||||
public const string DbServer = "DB_SERVER";
|
public const string DbServer = "DB_SERVER";
|
||||||
public const string DbUser = "DB_USER";
|
public const string DbUser = "DB_USER";
|
||||||
public const string DbPassword = "DB_PASSWORD";
|
public const string DbPassword = "DB_PASSWORD";
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Infrastructure.FileUpload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for storing and retrieving uploaded files in S3-compatible object storage.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFileStorageProvider
|
||||||
|
{
|
||||||
|
/// <summary>Uploads a file to the configured bucket.</summary>
|
||||||
|
/// <param name="key">The object key (path) to store the file under.</param>
|
||||||
|
/// <param name="content">The file content.</param>
|
||||||
|
/// <param name="contentType">The MIME type of the file.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the upload.</param>
|
||||||
|
Task UploadAsync(
|
||||||
|
string key,
|
||||||
|
Stream content,
|
||||||
|
string contentType,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>Deletes a file from the configured bucket.</summary>
|
||||||
|
/// <param name="key">The object key of the file to delete.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the deletion.</param>
|
||||||
|
Task DeleteAsync(string key, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>Generates a time-limited URL for retrieving a file directly from storage.</summary>
|
||||||
|
/// <param name="key">The object key of the file.</param>
|
||||||
|
/// <param name="expiresIn">How long the URL remains valid.</param>
|
||||||
|
string GetPresignedUrl(string key, TimeSpan expiresIn);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Infrastructure.FileUpload</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AWSSDK.S3" Version="4.0.102.4" />
|
||||||
|
<PackageReference
|
||||||
|
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||||
|
Version="10.0.1"
|
||||||
|
/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using Amazon.S3;
|
||||||
|
using Amazon.S3.Model;
|
||||||
|
using Infrastructure.Configuration;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace Infrastructure.FileUpload;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File storage service backed by an S3-compatible object store (e.g. SeaweedFS, AWS S3).
|
||||||
|
/// Configured via application configuration (backed by environment variables).
|
||||||
|
/// </summary>
|
||||||
|
public class S3FileStorageProvider : IFileStorageProvider, IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _bucket;
|
||||||
|
private readonly IAmazonS3 _client;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="S3FileStorageProvider" /> class, reading storage
|
||||||
|
/// configuration (<c>SEAWEEDFS_SERVICE_URL</c>, <c>SEAWEEDFS_ACCESS_KEY_ID</c>,
|
||||||
|
/// <c>SEAWEEDFS_SECRET_ACCESS_KEY</c>, <c>SEAWEEDFS_BUCKET</c>) from <paramref name="configuration" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Thrown when any of the required configuration values is not set.
|
||||||
|
/// </exception>
|
||||||
|
public S3FileStorageProvider(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
string serviceUrl = ConfigurationHelpers.GetKeyOrThrow(
|
||||||
|
configuration,
|
||||||
|
ConfigurationKeys.SeaweedFsServiceUrl
|
||||||
|
);
|
||||||
|
string accessKeyId = ConfigurationHelpers.GetKeyOrThrow(
|
||||||
|
configuration,
|
||||||
|
ConfigurationKeys.SeaweedFsAccessKeyId
|
||||||
|
);
|
||||||
|
string secretAccessKey = ConfigurationHelpers.GetKeyOrThrow(
|
||||||
|
configuration,
|
||||||
|
ConfigurationKeys.SeaweedFsSecretAccessKey
|
||||||
|
);
|
||||||
|
|
||||||
|
_bucket = ConfigurationHelpers.GetKeyOrThrow(
|
||||||
|
configuration,
|
||||||
|
ConfigurationKeys.SeaweedFsBucket
|
||||||
|
);
|
||||||
|
|
||||||
|
AmazonS3Config config = new() { ServiceURL = serviceUrl, ForcePathStyle = true };
|
||||||
|
|
||||||
|
_client = new AmazonS3Client(accessKeyId, secretAccessKey, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task UploadAsync(
|
||||||
|
string key,
|
||||||
|
Stream content,
|
||||||
|
string contentType,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
PutObjectRequest request = new()
|
||||||
|
{
|
||||||
|
BucketName = _bucket,
|
||||||
|
Key = key,
|
||||||
|
InputStream = content,
|
||||||
|
ContentType = contentType,
|
||||||
|
};
|
||||||
|
|
||||||
|
await _client.PutObjectAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task DeleteAsync(string key, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
DeleteObjectRequest request = new() { BucketName = _bucket, Key = key };
|
||||||
|
|
||||||
|
await _client.DeleteObjectAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string GetPresignedUrl(string key, TimeSpan expiresIn)
|
||||||
|
{
|
||||||
|
GetPreSignedUrlRequest request = new()
|
||||||
|
{
|
||||||
|
BucketName = _bucket,
|
||||||
|
Key = key,
|
||||||
|
Expires = DateTime.UtcNow.Add(expiresIn),
|
||||||
|
};
|
||||||
|
|
||||||
|
return _client.GetPreSignedURL(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_client.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user