Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9edb2aa4aa | |||
| ccf30ac36b | |||
| 6ed023f2cf | |||
| d6fdf1b9c8 |
@@ -14,18 +14,20 @@ public class CommonFixture : IDisposable
|
|||||||
{
|
{
|
||||||
Configuration = new ConfigurationBuilder()
|
Configuration = new ConfigurationBuilder()
|
||||||
.SetBasePath(Directory.GetCurrentDirectory())
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
.AddJsonFile("appsettings.json")
|
|
||||||
.AddUserSecrets<CommonFixture>()
|
.AddUserSecrets<CommonFixture>()
|
||||||
|
.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.json"), optional: true, reloadOnChange: true)
|
||||||
.AddEnvironmentVariables()
|
.AddEnvironmentVariables()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
Services = new ServiceCollection()
|
Services = new ServiceCollection()
|
||||||
.AddMediator()
|
.AddMediator()
|
||||||
.AddLogging()
|
.AddLogging()
|
||||||
.AddShopServices()
|
.AddShopServices()
|
||||||
.AddEmailServiceBus()
|
.AddEmailServiceBus()
|
||||||
|
.AddGarageS3(Configuration)
|
||||||
.AddShopDatabase(Configuration)
|
.AddShopDatabase(Configuration)
|
||||||
.AddEmailServices(Configuration)
|
.AddEmailServices(Configuration)
|
||||||
|
.AddSingleton(Configuration)
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
|
|
||||||
Mediator = Services.GetRequiredService<IMediator>();
|
Mediator = Services.GetRequiredService<IMediator>();
|
||||||
|
|||||||
@@ -27,10 +27,10 @@
|
|||||||
|
|
||||||
<!-- Global Usings -->
|
<!-- Global Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="Mediator"/>
|
<Using Include="Mediator" />
|
||||||
<Using Include="Xunit.Abstractions"/>
|
<Using Include="Xunit.Abstractions" />
|
||||||
<Using Include="Microsoft.Extensions.DependencyInjection"/>
|
<Using Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
<Using Include="Microsoft.Extensions.Configuration"/>
|
<Using Include="Microsoft.Extensions.Configuration" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="appsettings.json">
|
<None Update="appsettings.json">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using LiteCharms.Features.S3.Abstractions;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.Tests;
|
||||||
|
|
||||||
|
public class S3ServiceFeatureTests(CommonFixture fixture, ITestOutputHelper output) : IClassFixture<CommonFixture>
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task BookshopS3Service_MustReturnUrl()
|
||||||
|
{
|
||||||
|
var service = fixture.Services.GetKeyedService<IS3Service>(S3.Constants.BookshopBucketName);
|
||||||
|
|
||||||
|
var fileName = "appsettings.json";
|
||||||
|
|
||||||
|
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
|
||||||
|
|
||||||
|
Assert.True(File.Exists(path));
|
||||||
|
|
||||||
|
var stream = File.OpenRead(path);
|
||||||
|
|
||||||
|
var result = await service!.UploadFileAsync(fileName, stream, MimeKit.MimeTypes.GetMimeType(fileName));
|
||||||
|
|
||||||
|
Assert.True(result.IsSuccess);
|
||||||
|
Assert.NotNull(result.Value);
|
||||||
|
Assert.NotEmpty(result.Value);
|
||||||
|
|
||||||
|
output.WriteLine(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task BookshopS3Service_MustDeleteFile()
|
||||||
|
{
|
||||||
|
var service = fixture.Services.GetKeyedService<IS3Service>(S3.Constants.BookshopBucketName);
|
||||||
|
|
||||||
|
var fileName = "appsettings.json";
|
||||||
|
|
||||||
|
string path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
|
||||||
|
|
||||||
|
Assert.True(File.Exists(path));
|
||||||
|
|
||||||
|
var stream = File.OpenRead(path);
|
||||||
|
|
||||||
|
var uploadResult = await service!.UploadFileAsync(fileName, stream, MimeKit.MimeTypes.GetMimeType(fileName));
|
||||||
|
|
||||||
|
Assert.True(uploadResult.IsSuccess);
|
||||||
|
Assert.NotNull(uploadResult.Value);
|
||||||
|
Assert.NotEmpty(uploadResult.Value);
|
||||||
|
|
||||||
|
var fileKey = uploadResult.Value.Split('/').Last();
|
||||||
|
|
||||||
|
var deleteResult = await service!.DeleteFileAsync(fileKey);
|
||||||
|
|
||||||
|
Assert.True(deleteResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,16 @@
|
|||||||
{
|
{
|
||||||
|
"BookshopS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop",
|
||||||
|
"CdnBaseUrl": "https://bookshop.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
|
"BookshopQuotesS3Settings": {
|
||||||
|
"ServiceUrl": "http://192.168.1.177:30900",
|
||||||
|
"Region": "garage",
|
||||||
|
"BucketName": "bookshop.quotes",
|
||||||
|
"CdnBaseUrl": "https://bookshop.quotes.cdn.khongisa.co.za"
|
||||||
|
},
|
||||||
"Email": {
|
"Email": {
|
||||||
"Credentials": {
|
"Credentials": {
|
||||||
"Username": "shop@litecharms.co.za"
|
"Username": "shop@litecharms.co.za"
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
public static class Hash
|
public static class Hash
|
||||||
{
|
{
|
||||||
public static Func<string?, string?> GenerateSha256HashString = (input) =>
|
public static Func<string?, string?> StringToSha256Hash = (input) =>
|
||||||
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
|
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input!)));
|
||||||
|
|
||||||
|
public static Func<Stream, string?> StreamToSha256Hash = (stream) =>
|
||||||
|
Convert.ToHexString(SHA256.HashData(stream));
|
||||||
|
|
||||||
|
public static Func<byte[], string?> BytesToSha256Hash = (bytes) =>
|
||||||
|
Convert.ToHexString(SHA256.HashData(bytes));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace LiteCharms.Features.Extensions;
|
|||||||
public static class S3
|
public static class S3
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddGarageS3(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddGarageS3(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
if (configuration.GetSection(BookshopBucketName) is not null)
|
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value))
|
||||||
{
|
{
|
||||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopBucketName, (provider, client) =>
|
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopBucketName, (provider, client) =>
|
||||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopS3SettingsSection}:AccessKey").Value,
|
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopS3SettingsSection}:AccessKey").Value,
|
||||||
@@ -18,39 +18,45 @@ public static class S3
|
|||||||
ServiceURL = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value,
|
ServiceURL = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value,
|
||||||
AuthenticationRegion = configuration.GetSection($"{BookshopS3SettingsSection}:Region").Value,
|
AuthenticationRegion = configuration.GetSection($"{BookshopS3SettingsSection}:Region").Value,
|
||||||
ForcePathStyle = true,
|
ForcePathStyle = true,
|
||||||
|
EndpointDiscoveryEnabled = true,
|
||||||
|
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
services.AddKeyedScoped<IS3Service, BookstoreS3Service>(BookshopBucketName);
|
services.AddKeyedScoped<IS3Service, BookshopS3Service>(BookshopBucketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.GetSection(BookshopInvoicesBucketName) is not null)
|
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:ServiceUrl").Value))
|
||||||
{
|
{
|
||||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopInvoicesBucketName, (provider, client) =>
|
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopInvoicesBucketName, (provider, client) =>
|
||||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopInvoicesBucketName}:AccessKey").Value,
|
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:AccessKey").Value,
|
||||||
configuration.GetSection($"{BookshopInvoicesBucketName}:SecretKey").Value),
|
configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:SecretKey").Value),
|
||||||
new AmazonS3Config
|
new AmazonS3Config
|
||||||
{
|
{
|
||||||
ServiceURL = configuration.GetSection($"{BookshopInvoicesBucketName}:ServiceUrl").Value,
|
ServiceURL = configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:ServiceUrl").Value,
|
||||||
AuthenticationRegion = configuration.GetSection($"{BookshopInvoicesBucketName}:Region").Value,
|
AuthenticationRegion = configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:Region").Value,
|
||||||
ForcePathStyle = true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
services.AddKeyedScoped<IS3Service, BookstoreInvoicesS3Service>(BookshopInvoicesBucketName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.GetSection(BookshopQuotesBucketName) is not null)
|
|
||||||
{
|
|
||||||
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopQuotesBucketName, (provider, client) =>
|
|
||||||
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopQuotesBucketName}:AccessKey").Value,
|
|
||||||
configuration.GetSection($"{BookshopQuotesBucketName}:SecretKey").Value),
|
|
||||||
new AmazonS3Config
|
|
||||||
{
|
|
||||||
ServiceURL = configuration.GetSection($"{BookshopQuotesBucketName}:ServiceUrl").Value,
|
|
||||||
AuthenticationRegion = configuration.GetSection($"{BookshopQuotesBucketName}:Region").Value,
|
|
||||||
ForcePathStyle = true,
|
ForcePathStyle = true,
|
||||||
|
EndpointDiscoveryEnabled = true,
|
||||||
|
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
services.AddKeyedScoped<IS3Service, BookstoreQuotesS3Service>(BookshopQuotesBucketName);
|
services.AddKeyedScoped<IS3Service, BookshopInvoicesS3Service>(BookshopInvoicesBucketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(configuration.GetSection($"{BookshopQuotesS3SettingsSection}:ServiceUrl").Value))
|
||||||
|
{
|
||||||
|
services.AddKeyedSingleton<IAmazonS3, AmazonS3Client>(BookshopQuotesBucketName, (provider, client) =>
|
||||||
|
new AmazonS3Client(new BasicAWSCredentials(configuration.GetSection($"{BookshopQuotesS3SettingsSection}:AccessKey").Value,
|
||||||
|
configuration.GetSection($"{BookshopQuotesS3SettingsSection}:SecretKey").Value),
|
||||||
|
new AmazonS3Config
|
||||||
|
{
|
||||||
|
ServiceURL = configuration.GetSection($"{BookshopQuotesS3SettingsSection}:ServiceUrl").Value,
|
||||||
|
AuthenticationRegion = configuration.GetSection($"{BookshopQuotesS3SettingsSection}:Region").Value,
|
||||||
|
ForcePathStyle = true,
|
||||||
|
EndpointDiscoveryEnabled = true,
|
||||||
|
UseHttp = configuration.GetSection($"{BookshopS3SettingsSection}:ServiceUrl").Value!.Contains("http://")
|
||||||
|
}));
|
||||||
|
|
||||||
|
services.AddKeyedScoped<IS3Service, BookshopQuotesS3Service>(BookshopQuotesBucketName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
public interface IS3Service
|
public interface IS3Service
|
||||||
{
|
{
|
||||||
Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default);
|
Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default);
|
||||||
|
Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,83 @@
|
|||||||
namespace LiteCharms.Features.S3.Abstractions;
|
using static LiteCharms.Features.Extensions.Hash;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.S3.Abstractions;
|
||||||
|
|
||||||
public abstract class S3ServiceBase(IAmazonS3 amazonS3)
|
public abstract class S3ServiceBase(IAmazonS3 amazonS3)
|
||||||
{
|
{
|
||||||
protected readonly IAmazonS3 client = amazonS3;
|
protected readonly IAmazonS3 Client = amazonS3;
|
||||||
|
|
||||||
public abstract Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default);
|
protected abstract string BucketName { get; }
|
||||||
|
protected abstract string CdnBaseUrl { get; }
|
||||||
|
|
||||||
|
public virtual async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(BucketName))
|
||||||
|
return Result.Fail<string>("Bucket name is not configured.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(CdnBaseUrl))
|
||||||
|
return Result.Fail<string>("CDN base URL is not configured.");
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
|
||||||
|
await fileStream.CopyToAsync(stream, cancellationToken);
|
||||||
|
await fileStream.DisposeAsync();
|
||||||
|
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var fileHash = StreamToSha256Hash(stream);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(fileHash))
|
||||||
|
return Result.Fail<string>("Failed to compute file hash.");
|
||||||
|
|
||||||
|
var fileKey = $"{fileHash.ToLower()}{Path.GetExtension(fileName)}";
|
||||||
|
|
||||||
|
var putRequest = new PutObjectRequest
|
||||||
|
{
|
||||||
|
BucketName = BucketName,
|
||||||
|
Key = fileKey,
|
||||||
|
InputStream = stream,
|
||||||
|
ContentType = contentType,
|
||||||
|
UseChunkEncoding = false
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var response = await Client.PutObjectAsync(putRequest, cancellationToken);
|
||||||
|
|
||||||
|
return response.HttpStatusCode != System.Net.HttpStatusCode.OK
|
||||||
|
? Result.Fail<string>($"Failed to upload {fileName} to S3.")
|
||||||
|
: Result.Ok($"{CdnBaseUrl}/{fileKey}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(BucketName))
|
||||||
|
return Result.Fail("Bucket name is not configured.");
|
||||||
|
|
||||||
|
var deleteRequest = new DeleteObjectRequest
|
||||||
|
{
|
||||||
|
BucketName = BucketName,
|
||||||
|
Key = fileKey
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await Client.DeleteObjectAsync(deleteRequest, cancellationToken);
|
||||||
|
|
||||||
|
return response.HttpStatusCode != System.Net.HttpStatusCode.NoContent
|
||||||
|
? Result.Fail($"Failed to delete {fileKey} from S3.")
|
||||||
|
: Result.Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.Fail(new Error($"Error deleting {fileKey} from S3: {ex.Message}").CausedBy(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using LiteCharms.Features.S3.Abstractions;
|
||||||
|
using static LiteCharms.Features.S3.Constants;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.S3;
|
||||||
|
|
||||||
|
public class BookshopInvoicesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopInvoicesBucketName)] IAmazonS3 amazonS3) :
|
||||||
|
S3ServiceBase(amazonS3), IS3Service
|
||||||
|
{
|
||||||
|
protected override string BucketName => configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:BucketName").Value ?? "";
|
||||||
|
protected override string CdnBaseUrl => configuration.GetSection($"{BookshopInvoicesS3SettingsSection}:CdnBaseUrl").Value ?? "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using LiteCharms.Features.S3.Abstractions;
|
||||||
|
using static LiteCharms.Features.S3.Constants;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.S3;
|
||||||
|
|
||||||
|
public class BookshopQuotesS3Service(IConfiguration configuration, [FromKeyedServices(BookshopQuotesBucketName)] IAmazonS3 amazonS3) :
|
||||||
|
S3ServiceBase(amazonS3), IS3Service
|
||||||
|
{
|
||||||
|
protected override string BucketName => configuration.GetSection($"{BookshopQuotesS3SettingsSection}:BucketName").Value ?? "";
|
||||||
|
protected override string CdnBaseUrl => configuration.GetSection($"{BookshopQuotesS3SettingsSection}:CdnBaseUrl").Value ?? "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using LiteCharms.Features.S3.Abstractions;
|
||||||
|
using static LiteCharms.Features.S3.Constants;
|
||||||
|
|
||||||
|
namespace LiteCharms.Features.S3;
|
||||||
|
|
||||||
|
public class BookshopS3Service(IConfiguration configuration, [FromKeyedServices(BookshopBucketName)] IAmazonS3 amazonS3) :
|
||||||
|
S3ServiceBase(amazonS3), IS3Service
|
||||||
|
{
|
||||||
|
protected override string BucketName => configuration.GetSection($"{BookshopS3SettingsSection}:BucketName").Value ?? "";
|
||||||
|
protected override string CdnBaseUrl => configuration.GetSection($"{BookshopS3SettingsSection}:CdnBaseUrl").Value ?? "";
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using LiteCharms.Features.S3.Abstractions;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.S3;
|
|
||||||
|
|
||||||
public class BookstoreInvoicesS3Service(IConfiguration configuration, [FromKeyedServices(Constants.BookshopInvoicesBucketName)] IAmazonS3 amazonS3) :
|
|
||||||
S3ServiceBase(amazonS3), IS3Service
|
|
||||||
{
|
|
||||||
public override async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bucketName = configuration.GetSection($"{Constants.BookshopInvoicesS3SettingsSection}:BucketName").Value!;
|
|
||||||
var cdnBaseUrl = configuration.GetSection($"{Constants.BookshopInvoicesS3SettingsSection}:CdnBaseUrl").Value!;
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(bucketName))
|
|
||||||
return Result.Fail<string>("Bucket name is not configured.");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(cdnBaseUrl))
|
|
||||||
return Result.Fail<string>("CDN base URL is not configured.");
|
|
||||||
|
|
||||||
var response = await client.PutObjectAsync(new PutObjectRequest
|
|
||||||
{
|
|
||||||
BucketName = bucketName,
|
|
||||||
Key = fileName,
|
|
||||||
InputStream = fileStream,
|
|
||||||
ContentType = contentType
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
return response.HttpStatusCode != System.Net.HttpStatusCode.OK
|
|
||||||
? Result.Fail<string>($"Failed to upload {fileName} to S3.")
|
|
||||||
: Result.Ok(string.Format(cdnBaseUrl, bucketName, fileName));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using LiteCharms.Features.S3.Abstractions;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.S3;
|
|
||||||
|
|
||||||
public class BookstoreQuotesS3Service(IConfiguration configuration, [FromKeyedServices(Constants.BookshopQuotesBucketName)] IAmazonS3 amazonS3) :
|
|
||||||
S3ServiceBase(amazonS3), IS3Service
|
|
||||||
{
|
|
||||||
public override async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bucketName = configuration.GetSection($"{Constants.BookshopQuotesS3SettingsSection}:BucketName").Value!;
|
|
||||||
var cdnBaseUrl = configuration.GetSection($"{Constants.BookshopQuotesS3SettingsSection}:CdnBaseUrl").Value!;
|
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(bucketName))
|
|
||||||
return Result.Fail<string>("Bucket name is not configured.");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(cdnBaseUrl))
|
|
||||||
return Result.Fail<string>("CDN base URL is not configured.");
|
|
||||||
|
|
||||||
var response = await client.PutObjectAsync(new PutObjectRequest
|
|
||||||
{
|
|
||||||
BucketName = bucketName,
|
|
||||||
Key = fileName,
|
|
||||||
InputStream = fileStream,
|
|
||||||
ContentType = contentType
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
return response.HttpStatusCode != System.Net.HttpStatusCode.OK
|
|
||||||
? Result.Fail<string>($"Failed to upload {fileName} to S3.")
|
|
||||||
: Result.Ok(string.Format(cdnBaseUrl, bucketName, fileName));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using LiteCharms.Features.S3.Abstractions;
|
|
||||||
|
|
||||||
namespace LiteCharms.Features.S3;
|
|
||||||
|
|
||||||
public class BookstoreS3Service(IConfiguration configuration, [FromKeyedServices(Constants.BookshopBucketName)] IAmazonS3 amazonS3) :
|
|
||||||
S3ServiceBase(amazonS3), IS3Service
|
|
||||||
{
|
|
||||||
private readonly string bucketName = configuration.GetSection($"{Constants.BookshopS3SettingsSection}:BucketName").Value ?? "";
|
|
||||||
private readonly string cdnBaseUrl = configuration.GetSection($"{Constants.BookshopS3SettingsSection}:CdnBaseUrl").Value ?? "";
|
|
||||||
|
|
||||||
public override async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(string.IsNullOrWhiteSpace(bucketName))
|
|
||||||
return Result.Fail<string>("Bucket name is not configured.");
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(cdnBaseUrl))
|
|
||||||
return Result.Fail<string>("CDN base URL is not configured.");
|
|
||||||
|
|
||||||
var response = await client.PutObjectAsync(new PutObjectRequest
|
|
||||||
{
|
|
||||||
BucketName = bucketName,
|
|
||||||
Key = fileName,
|
|
||||||
InputStream = fileStream,
|
|
||||||
ContentType = contentType
|
|
||||||
}, cancellationToken);
|
|
||||||
|
|
||||||
return response.HttpStatusCode != System.Net.HttpStatusCode.OK
|
|
||||||
? Result.Fail<string>($"Failed to upload {fileName} to S3.")
|
|
||||||
: Result.Ok(string.Format(cdnBaseUrl, bucketName, fileName));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,7 @@ public class LeadService(IDbContextFactory<ShopDbContext> contextFactory)
|
|||||||
FeedItemId = request.FeedItemId,
|
FeedItemId = request.FeedItemId,
|
||||||
Status = LeadStatus.New,
|
Status = LeadStatus.New,
|
||||||
TargetId = request.TargetId,
|
TargetId = request.TargetId,
|
||||||
AttributionHash = GenerateSha256HashString.Invoke($"{request.ClickId}{request.AppClickId}{request.WebClickId}")
|
AttributionHash = StringToSha256Hash.Invoke($"{request.ClickId}{request.AppClickId}{request.WebClickId}")
|
||||||
});
|
});
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user