Browse Source

dynamic password retrieval via a distributed cache in BunnyClientFactory

pull/22004/head
Suhaib 2 years ago
parent
commit
6d70e78cbd
  1. 4
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.csproj
  2. 10
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/AbpBunnyBlobStoringModule .cs
  3. 97
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProvider.cs
  4. 229
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyClient.cs
  5. 153
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyClientFactory.cs
  6. 10
      framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyClientFactory.cs

4
framework/src/Volo.Abp.BlobStoring.Bunny/Volo.Abp.BlobStoring.Bunny.csproj

@ -15,10 +15,12 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" /> <ProjectReference Include="..\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
</ItemGroup> <ProjectReference Include="..\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BunnyCDN.Net.Storage" /> <PackageReference Include="BunnyCDN.Net.Storage" />
<PackageReference Include="Microsoft.Extensions.Http" />
</ItemGroup> </ItemGroup>
</Project> </Project>

10
framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/AbpBunnyBlobStoringModule .cs

@ -1,11 +1,17 @@
using Volo.Abp.Modularity; using System;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Modularity;
namespace Volo.Abp.BlobStoring.Bunny; namespace Volo.Abp.BlobStoring.Bunny;
[DependsOn(typeof(AbpBlobStoringModule))] [DependsOn(
typeof(AbpBlobStoringModule),
typeof(AbpCachingModule))]
public class AbpBlobStoringBunnyModule : AbpModule public class AbpBlobStoringBunnyModule : AbpModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.AddHttpClient();
} }
} }

97
framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyBlobProvider.cs

@ -12,13 +12,16 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
{ {
protected IBunnyBlobNameCalculator BunnyBlobNameCalculator { get; } protected IBunnyBlobNameCalculator BunnyBlobNameCalculator { get; }
protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; } protected IBlobNormalizeNamingService BlobNormalizeNamingService { get; }
protected IBunnyClientFactory BunnyClientFactory { get; }
public BunnyBlobProvider( public BunnyBlobProvider(
IBunnyBlobNameCalculator bunnyBlobNameCalculator, IBunnyBlobNameCalculator bunnyBlobNameCalculator,
IBlobNormalizeNamingService blobNormalizeNamingService) IBlobNormalizeNamingService blobNormalizeNamingService,
IBunnyClientFactory bunnyClientFactory)
{ {
BunnyBlobNameCalculator = bunnyBlobNameCalculator; BunnyBlobNameCalculator = bunnyBlobNameCalculator;
BlobNormalizeNamingService = blobNormalizeNamingService; BlobNormalizeNamingService = blobNormalizeNamingService;
BunnyClientFactory = bunnyClientFactory;
} }
public override async Task SaveAsync(BlobProviderSaveArgs args) public override async Task SaveAsync(BlobProviderSaveArgs args)
@ -27,14 +30,14 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
var containerName = GetContainerName(args); var containerName = GetContainerName(args);
var blobName = BunnyBlobNameCalculator.Calculate(args); var blobName = BunnyBlobNameCalculator.Calculate(args);
using var bunnyClient = GetBunnyClient(args); await ValidateContainerExistsAsync(containerName, configuration);
await ValidateContainerExistsAsync(bunnyClient, containerName, configuration);
if (!args.OverrideExisting && await BlobExistsAsync(bunnyClient, containerName, blobName)) var bunnyStorage = await GetBunnyCDNStorageAsync(args);
if (!args.OverrideExisting && await BlobExistsAsync(bunnyStorage, containerName, blobName))
{ {
throw new BlobAlreadyExistsException( throw new BlobAlreadyExistsException(
$"BLOB '{args.BlobName}' already exists in container '{containerName}'. " + $"Blob '{args.BlobName}' already exists in container '{containerName}'. " +
$"Set {nameof(args.OverrideExisting)} to true to overwrite."); $"Set {nameof(args.OverrideExisting)} to true to overwrite.");
} }
@ -42,57 +45,53 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
await args.BlobStream.CopyToAsync(memoryStream); await args.BlobStream.CopyToAsync(memoryStream);
memoryStream.Position = 0; memoryStream.Position = 0;
await bunnyClient.UploadAsync(memoryStream, $"{containerName}/{blobName}"); await bunnyStorage.UploadAsync(memoryStream, $"{containerName}/{blobName}");
} }
public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args) public override async Task<bool> DeleteAsync(BlobProviderDeleteArgs args)
{ {
var blobName = BunnyBlobNameCalculator.Calculate(args); var blobName = BunnyBlobNameCalculator.Calculate(args);
var configuration = args.Configuration.GetBunnyConfiguration();
var containerName = GetContainerName(args); var containerName = GetContainerName(args);
using var bunnyClient = GetBunnyClient(args); var bunnyStorage = await GetBunnyCDNStorageAsync(args);
if (!await BlobExistsAsync(bunnyClient, containerName, blobName))
if (!await BlobExistsAsync(bunnyStorage, containerName, blobName))
{ {
return false; return false;
} }
try try
{ {
await bunnyClient.DeleteObjectAsync($"{containerName}/{blobName}"); return await bunnyStorage.DeleteObjectAsync($"{containerName}/{blobName}");
return true;
} }
catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404")) catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404"))
{ {
return false; return false;
} }
} }
public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args) public override async Task<bool> ExistsAsync(BlobProviderExistsArgs args)
{ {
var blobName = BunnyBlobNameCalculator.Calculate(args); var blobName = BunnyBlobNameCalculator.Calculate(args);
var containerName = GetContainerName(args); var containerName = GetContainerName(args);
var configuration = args.Configuration.GetBunnyConfiguration(); var bunnyStorage = await GetBunnyCDNStorageAsync(args);
using var bunnyClient = GetBunnyClient(args);
return await BlobExistsAsync(bunnyClient, containerName, blobName); return await BlobExistsAsync(bunnyStorage, containerName, blobName);
} }
public override async Task<Stream?> GetOrNullAsync(BlobProviderGetArgs args) public override async Task<Stream?> GetOrNullAsync(BlobProviderGetArgs args)
{ {
var blobName = BunnyBlobNameCalculator.Calculate(args); var blobName = BunnyBlobNameCalculator.Calculate(args);
var containerName = GetContainerName(args); var containerName = GetContainerName(args);
var configuration = args.Configuration.GetBunnyConfiguration(); var bunnyStorage = await GetBunnyCDNStorageAsync(args);
using var bunnyClient = GetBunnyClient(args);
if (!await BlobExistsAsync(bunnyClient, containerName, blobName)) if (!await BlobExistsAsync(bunnyStorage, containerName, blobName))
{ {
return null; return null;
} }
try try
{ {
return await bunnyClient.DownloadObjectAsStreamAsync($"{containerName}/{blobName}"); return await bunnyStorage.DownloadObjectAsStreamAsync($"{containerName}/{blobName}");
} }
catch (WebException ex) when ((HttpStatusCode)ex.Status == HttpStatusCode.NotFound) catch (WebException ex) when ((HttpStatusCode)ex.Status == HttpStatusCode.NotFound)
{ {
@ -100,14 +99,11 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
} }
} }
protected virtual async Task<bool> BlobExistsAsync(BunnyClient bunnyClient, string containerName, string blobName) protected virtual async Task<bool> BlobExistsAsync(BunnyCDNStorage bunnyStorage, string containerName, string blobName)
{ {
try try
{ {
// Combine container name and blob name to create the full path
var fullBlobPath = $"/{containerName}/{blobName}"; var fullBlobPath = $"/{containerName}/{blobName}";
// Extract the directory path
var directoryPath = Path.GetDirectoryName(fullBlobPath)?.Replace('\\', '/') + "/"; var directoryPath = Path.GetDirectoryName(fullBlobPath)?.Replace('\\', '/') + "/";
if (string.IsNullOrWhiteSpace(directoryPath)) if (string.IsNullOrWhiteSpace(directoryPath))
@ -115,9 +111,7 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
throw new Exception("Invalid directory path generated from blob name."); throw new Exception("Invalid directory path generated from blob name.");
} }
var objects = await bunnyClient.GetStorageObjectsAsync(directoryPath); var objects = await bunnyStorage.GetStorageObjectsAsync(directoryPath);
// Check if the specific blob exists in the returned objects
return objects?.Any(o => o.FullPath == fullBlobPath) == true; return objects?.Any(o => o.FullPath == fullBlobPath) == true;
} }
catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404")) catch (BunnyCDNStorageException ex) when (ex.Message.Contains("404"))
@ -129,16 +123,19 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
throw new Exception($"Error while checking blob existence: {ex.Message}", ex); throw new Exception($"Error while checking blob existence: {ex.Message}", ex);
} }
} }
protected virtual BunnyClient GetBunnyClient(BlobProviderArgs args)
protected virtual async Task<BunnyCDNStorage> GetBunnyCDNStorageAsync(BlobProviderArgs args)
{ {
var containerName = GetContainerName(args);
var configuration = args.Configuration.GetBunnyConfiguration(); var configuration = args.Configuration.GetBunnyConfiguration();
if (configuration.Region.IsNullOrEmpty()) var containerName = GetContainerName(args);
{ var region = configuration.Region ?? "de";
return new BunnyClient(configuration.AccessKey, containerName);
} return await BunnyClientFactory.CreateAsync(
return new BunnyClient(configuration.AccessKey, containerName, configuration.Region); configuration.AccessKey,
containerName,
region);
} }
protected virtual string GetContainerName(BlobProviderArgs args) protected virtual string GetContainerName(BlobProviderArgs args)
{ {
var configuration = args.Configuration.GetBunnyConfiguration(); var configuration = args.Configuration.GetBunnyConfiguration();
@ -146,23 +143,25 @@ public class BunnyBlobProvider : BlobProviderBase, ITransientDependency
? args.ContainerName ? args.ContainerName
: BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!); : BlobNormalizeNamingService.NormalizeContainerName(args.Configuration, configuration.ContainerName!);
} }
protected virtual async Task ValidateContainerExistsAsync( protected virtual async Task ValidateContainerExistsAsync(
BunnyClient client, string containerName,
string containerName, BunnyBlobProviderConfiguration configuration
BunnyBlobProviderConfiguration configuration) )
{ {
if (configuration.CreateContainerIfNotExists) try
{ {
try await BunnyClientFactory.EnsureStorageZoneExistsAsync(
{ configuration.AccessKey,
await client.EnsureStorageZoneExistsAsync(containerName); containerName,
} configuration.Region ?? "de",
catch (BunnyApiException ex) configuration.CreateContainerIfNotExists);
{ }
throw new AbpException( catch (Exception ex)
$"Failed to ensure Bunny storage zone '{containerName}' exists: {ex.Message}", {
ex); throw new AbpException(
} $"Failed to validate storage zone '{containerName}': {ex.Message}",
ex);
} }
} }
} }

229
framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyClient.cs

@ -1,229 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using BunnyCDN.Net.Storage;
using BunnyCDN.Net.Storage.Models;
namespace Volo.Abp.BlobStoring.Bunny;
public class BunnyClient : IDisposable
{
private readonly string _region;
private readonly string _storageZoneName;
private readonly HttpClient _httpClient;
private static BunnyCDNStorage? BunnyCDNStorage = null;
public BunnyClient(string accessKey, string storageZoneName, string region = "de", HttpMessageHandler? handler = null)
{
if (string.IsNullOrWhiteSpace(accessKey))
{
throw new ArgumentException("Account access key must not be null or empty.", nameof(accessKey));
}
_region = region;
_storageZoneName = storageZoneName;
_httpClient = handler != null ? new HttpClient(handler) : new HttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(120);
_httpClient.DefaultRequestHeaders.Add("AccessKey", accessKey);
}
public async Task<bool> DoesStorageZoneExistAsync(string zoneName)
{
if (string.IsNullOrWhiteSpace(zoneName))
{
throw new ArgumentException("Zone name must not be null or empty.", nameof(zoneName));
}
try
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://api.bunny.net/storagezone?page=0&perPage=1000&includeDeleted=false"),
Headers = { { "accept", "application/json" } }
};
using var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var storageZones = JsonSerializer.Deserialize<List<StorageZone>>(responseBody);
return storageZones?.Any(zone => zone.Name!.Equals(zoneName, StringComparison.OrdinalIgnoreCase)) == true;
}
catch (HttpRequestException ex)
{
throw new BunnyApiException("Error occurred while checking storage zone existence.", ex);
}
catch (JsonException ex)
{
throw new BunnyApiException("Failed to parse the response from Bunny API.", ex);
}
}
// Ensure the storage zone exists, creating it if not found, and return the zone
public async Task<StorageZone> EnsureStorageZoneExistsAsync(string zoneName, string? originUrl = "")
{
try
{
if (!await DoesStorageZoneExistAsync(zoneName))
{
return await CreateStorageZoneAsync(zoneName, originUrl);
}
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://api.bunny.net/storagezone?page=0&perPage=1000&includeDeleted=false"),
Headers = { { "accept", "application/json" } }
};
using var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var storageZones = JsonSerializer.Deserialize<List<StorageZone>>(responseBody);
return storageZones?.FirstOrDefault(zone => zone.Name!.Equals(zoneName, StringComparison.OrdinalIgnoreCase))
?? throw new BunnyApiException($"Storage zone '{zoneName}' not found even though it should exist.");
}
catch (BunnyApiException ex)
{
throw new BunnyApiException($"Failed to ensure the existence of storage zone '{zoneName}'.", ex);
}
}
public async Task UploadAsync(Stream stream, string path) => await (await GetBunnyCDNStorage()).UploadAsync(stream, path);
public async Task<bool> DeleteObjectAsync(string path) => await (await GetBunnyCDNStorage()).DeleteObjectAsync(path);
public async Task<List<StorageObject>> GetStorageObjectsAsync(string path) => await (await GetBunnyCDNStorage()).GetStorageObjectsAsync(path);
public async Task<Stream> DownloadObjectAsStreamAsync(string path) => await (await GetBunnyCDNStorage()).DownloadObjectAsStreamAsync(path);
public async Task DeleteStorageZoneAsync(string zoneName)
{
if (string.IsNullOrWhiteSpace(zoneName))
{
throw new ArgumentException("Zone name must not be null or empty.", nameof(zoneName));
}
var storageZone = (await GetStorageZoneAsync());
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri($"https://api.bunny.net/storagezone/{storageZone.Id}")
};
using (var response = await _httpClient.SendAsync(request))
{
response.EnsureSuccessStatusCode();
}
}
private async Task<StorageZone> GetStorageZoneAsync()
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://api.bunny.net/storagezone")
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var zones = JsonSerializer.Deserialize<List<StorageZone>>(content);
var targetZone = zones?.FirstOrDefault(z =>
z.Name.Equals(_storageZoneName, StringComparison.OrdinalIgnoreCase));
return targetZone
?? throw new AbpException(
$"Storage zone '{_storageZoneName}' is not found");
}
private async Task<StorageZone> CreateStorageZoneAsync(string zoneName, string? originUrl = "", int zoneTier = 0)
{
if (string.IsNullOrWhiteSpace(zoneName))
{
throw new ArgumentException("Zone name must not be null or empty.", nameof(zoneName));
}
try
{
var payload = new Dictionary<string, object>
{
{ "ZoneTier", zoneTier },
{ "Name", zoneName },
{ "Region", _region }
};
// Add OriginUrl only if it is not empty
if (!string.IsNullOrEmpty(originUrl))
{
payload.Add("OriginUrl", originUrl!);
}
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://api.bunny.net/storagezone"),
Headers = { { "accept", "application/json" } },
Content = new StringContent(JsonSerializer.Serialize(payload))
{
Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
}
};
using var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
throw new BunnyApiException($"Failed to create storage zone. Response: {responseBody}");
}
var createdZoneJson = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<StorageZone>(createdZoneJson)
?? throw new BunnyApiException("Failed to deserialize the created storage zone response.");
}
catch (HttpRequestException ex)
{
throw new BunnyApiException("Error occurred while creating storage zone.", ex);
}
catch (JsonException ex)
{
throw new BunnyApiException("Failed to parse the response for the created storage zone.", ex);
}
}
private async Task<BunnyCDNStorage> GetBunnyCDNStorage()
{
if (BunnyCDNStorage != null) {
return BunnyCDNStorage;
}
var storageZonePassword = (await GetStorageZoneAsync()).Password;
BunnyCDNStorage = new BunnyCDNStorage(_storageZoneName, storageZonePassword, _region);
return BunnyCDNStorage;
}
public void Dispose() => _httpClient?.Dispose();
public class StorageZone
{
public int Id { get; set; }
public string Password { get; set; } = null!;
public string Name { get; set; } = null!;
public string? Region { get; set; } = null!;
public bool Deleted { get; set; }
}
}
public class BunnyApiException : Exception
{
public BunnyApiException(string message) : base(message) { }
public BunnyApiException(string message, Exception innerException) : base(message, innerException) { }
}

153
framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/BunnyClientFactory.cs

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BunnyCDN.Net.Storage;
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BlobStoring.Bunny;
public class BunnyClientFactory : IBunnyClientFactory, ITransientDependency
{
private readonly IDistributedCache<StorageZone> _cache;
private readonly IHttpClientFactory _httpClientFactory;
private const string CacheKeyPrefix = "BunnyStorageZone:";
private static TimeSpan CacheDuration = TimeSpan.FromHours(12);
public BunnyClientFactory(
IHttpClientFactory httpClient,
IDistributedCache<StorageZone> cache)
{
_cache = cache;
_httpClientFactory = httpClient;
}
public async Task<BunnyCDNStorage> CreateAsync(string accessKey, string containerName, string region = "de")
{
var cacheKey = $"{CacheKeyPrefix}{containerName}";
var storageZoneInfo = await _cache.GetOrAddAsync(
cacheKey,
async () => {
var result = await GetStorageZoneAsync(accessKey, containerName);
if (result == null)
{
throw new AbpException($"Storage zone '{containerName}' not found");
}
return result;
},
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.Add(CacheDuration)
}
);
if (storageZoneInfo == null)
{
throw new AbpException($"Could not retrieve storage zone information for container '{containerName}'");
}
return new BunnyCDNStorage(containerName, storageZoneInfo.Password, region);
}
public async Task EnsureStorageZoneExistsAsync(
string accessKey,
string containerName,
string region = "de",
bool createIfNotExists = false)
{
var storageZone = await GetStorageZoneAsync(accessKey, containerName);
if (storageZone == null)
{
if (!createIfNotExists)
{
throw new AbpException(
$"Storage zone '{containerName}' does not exist. " +
"Set createIfNotExists to true to create it automatically.");
}
await CreateStorageZoneAsync(accessKey, containerName, region);
// Clear the cache to force a refresh of the storage zone info
var cacheKey = $"{CacheKeyPrefix}{containerName}";
await _cache.RemoveAsync(cacheKey);
}
}
private async Task<StorageZone> CreateStorageZoneAsync(
string accessKey,
string containerName,
string region)
{
using var _client = _httpClientFactory.CreateClient("BunnyApiClient");
_client.DefaultRequestHeaders.Add("AccessKey", accessKey);
var payload = new Dictionary<string, object>
{
{ "Name", containerName },
{ "Region", region },
{ "ZoneTier", 0 }
};
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json");
var response = await _client.PostAsync(
"https://api.bunny.net/storagezone",
content);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new AbpException(
$"Failed to create storage zone '{containerName}'. " +
$"Status: {response.StatusCode}, Error: {errorContent}");
}
var responseContent = await response.Content.ReadAsStringAsync();
var createdZone = JsonSerializer.Deserialize<StorageZone>(responseContent);
if (createdZone == null)
{
throw new AbpException(
$"Failed to deserialize the created storage zone response for '{containerName}'");
}
return createdZone;
}
private async Task<StorageZone?> GetStorageZoneAsync(string accessKey, string containerName)
{
using var _client = _httpClientFactory.CreateClient("BunnyApiClient");
_client.DefaultRequestHeaders.Add("AccessKey", accessKey);
var response = await _client.GetAsync("https://api.bunny.net/storagezone");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var zones = JsonSerializer.Deserialize<StorageZone[]>(content);
return Array.Find(zones!, z =>
z.Name.Equals(containerName, StringComparison.OrdinalIgnoreCase) && !z.Deleted);
}
[Serializable]
public class StorageZone
{
public int Id { get; set; }
public string Password { get; set; } = null!;
public string Name { get; set; } = null!;
public string? Region { get; set; }
public bool Deleted { get; set; }
}
}
public class BunnyApiException : Exception
{
public BunnyApiException(string message) : base(message) { }
public BunnyApiException(string message, Exception innerException) : base(message, innerException) { }
}

10
framework/src/Volo.Abp.BlobStoring.Bunny/Volo/Abp/BlobStoring/Bunny/IBunnyClientFactory.cs

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using BunnyCDN.Net.Storage;
namespace Volo.Abp.BlobStoring.Bunny;
public interface IBunnyClientFactory
{
Task<BunnyCDNStorage> CreateAsync(string accessKey, string containerName, string region = "de");
Task EnsureStorageZoneExistsAsync(string accessKey, string containerName, string region = "de", bool createIfNotExists = false);
}
Loading…
Cancel
Save